plugin_dbui.py 12.8 KB
Newer Older
1 2
""" plugin_dbui.py

3
    Controllers expose by the plugin:
4

5 6 7 8 9
        about
        call
        csv
        dbui_conf
        documentation
10
        index
11 12 13
        latex2pdf
        satus
        version
14 15

"""
16
import json
17 18
import os

19 20
from gluon.tools import PluginManager

Renaud Le Gac's avatar
Renaud Le Gac committed
21
API = """
LE GAC Renaud's avatar
LE GAC Renaud committed
22
Dbui.csvUrl = "/%s/plugin_dbui/csv";
23 24
Dbui.config = %s;
Dbui.debug = %s;
LE GAC Renaud's avatar
LE GAC Renaud committed
25 26
Dbui.latex2pdfUrl = "/%s/plugin_dbui/latex2pdf.pdf";
Dbui.name = "%s";
27
Dbui.REMOTE_API = {
LE GAC Renaud's avatar
LE GAC Renaud committed
28 29 30
    "url": "/%s/plugin_dbui/call",
    "type": "remoting",
    "actions": %s
31
};"""
32 33


34
def about():
35 36
    fn = os.path.join("applications",
                      request.application,
37
                      PluginManager("dbui").dbui.app_about)
LE GAC Renaud's avatar
LE GAC Renaud committed
38
    return open(fn, "rb").read()
39 40


41
def call():
legac's avatar
legac committed
42
    """Action to handle the Ext.Direct protocol.
43

44 45 46 47
    """
    return directSvc()


48
def csv():
legac's avatar
legac committed
49
    """Action returning the content of a table as a CSV file.
50
    Foreign keys are replaced by the parent column.
51

52
    """
53
    from gluon.contenttype import contenttype
54 55 56
    from plugin_dbui import (get_foreign_field,
                             get_where_query,
                             is_foreign_field,
57
                             is_table_with_foreign_fields)
58

LE GAC Renaud's avatar
LE GAC Renaud committed
59
    tablename = request.vars["tableName"]
tux091's avatar
tux091 committed
60

LE GAC Renaud's avatar
LE GAC Renaud committed
61 62 63
    headers = response.headers
    headers["Content-Type"] = contenttype(".csv")
    headers["Content-Disposition"] = "attachment; filename=%s.csv;" % tablename
64

Renaud Le Gac's avatar
Renaud Le Gac committed
65 66
    table = db[tablename]
    query = table.id > 0
67

68 69
    # replace foreign key by parent fields
    # and build the query to resolve foreign keys
70
    if is_table_with_foreign_fields(table):
71

72
        fields = []
73
        query = ((query) & (get_where_query(table)))
74

Renaud Le Gac's avatar
Renaud Le Gac committed
75
        for field in table:
76

77 78
            if is_foreign_field(field):
                k_table, k_field, k_key = get_foreign_field(field)
79
                fields.append(db[k_table][k_field])
80

81
            else:
tux091's avatar
tux091 committed
82
                fields.append(field)
83
    else:
Renaud Le Gac's avatar
Renaud Le Gac committed
84
        fields = [table.ALL]
85

86
    # interrogate the database
87 88
    rows = db(query).select(*fields)
    return str(rows)
89

90

91
def dbui_conf():
legac's avatar
legac committed
92
    """Action returning the javascript script configuring
93
    the plugin dbui: application name, store definition,...
94

95
    """
96 97
    from plugin_dbui import (DBUI,
                             get_all_tables,
LE GAC Renaud's avatar
LE GAC Renaud committed
98
                             JSONEncoder,
99 100 101
                             to_jsonstore,
                             to_model,
                             to_treeNodes,
102
                             to_viewport)
103

104
    di = {}
105

106 107
    # build the dictionary required by Ext.direct
    # containing the definition of the remote procedure
108
    for k, f in list(directSvc.procedures.items()):
LE GAC Renaud's avatar
LE GAC Renaud committed
109
        action, method = k.split(".")
110

111
        if action == DBUI:
112
            nargs = f.__code__.co_argcount
113
        else:
114
            nargs = f.__func__.__code__.co_argcount - 1
115

116 117
        if action not in di:
            di[action] = []
118

LE GAC Renaud's avatar
LE GAC Renaud committed
119
        di[action].append({"name": method, "len": nargs})
120 121

    # the definition of the models
LE GAC Renaud's avatar
LE GAC Renaud committed
122 123 124 125
    config = {"models": {},
              "stores": {},
              "treeNodes": [],
              "viewport": None}
126

127
    for table in get_all_tables(db):
LE GAC Renaud's avatar
LE GAC Renaud committed
128
        config["models"][table._tablename] = to_model(table)
129

130
    # the stores configuration (static, for each table,...)
131
    # NOTE: the interface require a store for all tables including alias.
132 133
    # The only way to extract them is to scan the attributes list of
    # the DAL since the method db.tables() or any variant return
134
    # tables but not the alias one
LE GAC Renaud's avatar
LE GAC Renaud committed
135
    config["stores"].update(PluginManager("dbui").dbui.static_stores)
136 137
    for table in get_all_tables(db):
        cfg = to_jsonstore(table)
LE GAC Renaud's avatar
LE GAC Renaud committed
138
        config["stores"][cfg["storeId"]] = cfg
139

140
    # the tree nodes configuration for the TreeStore
LE GAC Renaud's avatar
LE GAC Renaud committed
141
    config["treeNodes"] = to_treeNodes()
142

143
    # the viewport configuration
LE GAC Renaud's avatar
LE GAC Renaud committed
144
    config["viewport"] = to_viewport()
145 146

    # debug mode
LE GAC Renaud's avatar
LE GAC Renaud committed
147 148 149
    debug = "false"
    if "debug" in request.vars:
        debug = "true"
150

151 152
    # fill the javascript template
    app = request.application
153
    script = API % (app,
LE GAC Renaud's avatar
LE GAC Renaud committed
154
                    json.dumps(config, cls=JSONEncoder),
155
                    debug,
Renaud Le Gac's avatar
Renaud Le Gac committed
156
                    app,
157
                    app,
158
                    app,
159
                    json.dumps(di))
160

161
    # return the response as a javascript
LE GAC Renaud's avatar
LE GAC Renaud committed
162
    response.headers["Content-Type"] = "text/javascript"
163
    return script
Renaud Le Gac's avatar
Renaud Le Gac committed
164 165


166 167 168 169 170 171
def documentations_table():
    """Documentation for users and developer display as table.

    Returns:
        Ext.data.Array:
            configuration for the documentation and for the source code.
172

173
    """
174
    from plugin_dbui import get_reference_paths, Store
175

LE GAC Renaud's avatar
LE GAC Renaud committed
176 177 178 179 180 181
    def path_exists(x):
        return x and os.path.exists(os.path.join(apath, x))

    def f(x):
        return x.replace("static/", "")

182
    plugin = PluginManager("dbui").dbui
183

184 185
    # alias
    a = '<a href="%s" target="_blank">%s</a>'
186 187

    trDev = T("Documentation for developers")
188 189
    trJS = T("Javascript API")
    trPy = T("Python API")
190

191 192
    apath, lpath = get_reference_paths()

193
    # documentation of the application
194
    userdoc = ""
195 196
    path_db_schema = plugin.app_db_schema
    if path_exists(path_db_schema):
LE GAC Renaud's avatar
LE GAC Renaud committed
197
        userdoc = a % (URL("static", f(path_db_schema)), T("Data base scheme"))
198 199

    pydoc = ""
200 201
    path_html_api = plugin.app_html_api
    if path_exists(path_html_api):
LE GAC Renaud's avatar
LE GAC Renaud committed
202
        pydoc = a % (URL("static", f(path_html_api)), trPy)
203 204

    jsdoc = ""
205 206
    path_html_jsduck = plugin.app_html_jsduck
    if path_exists(path_html_jsduck):
LE GAC Renaud's avatar
LE GAC Renaud committed
207
        jsdoc = a % (URL("static", f(path_html_jsduck)), trJS)
208

209 210
    # configuration of the Ext.data.Store for the documentation
    cfg = Store()
211

LE GAC Renaud's avatar
LE GAC Renaud committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    cfg.fields = [
        dict(name="code", type="string"),
        dict(name="developer", type="string"),
        dict(name="python", type="string"),
        dict(name="javascript", type="string")]

    r1 = dict(
        code=request.application,
        developer=userdoc,
        python=pydoc,
        javascript=jsdoc)

    r2 = dict(
        code=a % ("https://marprod.in2p3.fr/plugin_dbui_book", "plugin_dbui"),

        developer=a %
        (URL("static", "plugin_dbui/docs/reference/index.html"), trDev),

        python=a % (URL("static", "plugin_dbui/docs/api/index.html"), trPy),

        javascript=a %
        (URL("static", "plugin_dbui/docs/jsduck/index.html"), trJS))

    r3 = dict(
        code=a % ("http://web2py.com/", "Web2py"),
        developer=a % ("http://web2py.com/book", trDev),
        python=a % ("http://web2py.readthedocs.org/en/latest/", trPy),
        javascript="")

    r4 = dict(
        code=a % ("http://www.sencha.com/products/extjs/", "Ext JS"),
        developer="",
        python="",
        javascript=a % ("http://docs.sencha.com/extjs/6.0.1-classic/", trJS))

    r5 = dict(
        code=a % ("http://www.mathjax.org/", "MathJax"),
        developer="",
        python="",
        javascript=a % ("http://docs.mathjax.org/", trJS))

    r6 = dict(
        code=a % ("http://ace.c9.io/#nav=about", "Ace"),
        developer="",
        python="",
        javascript=a % ("http://ace.c9.io/#nav=api", trJS))
258

259
    cfg.data = [r1, r2, r3, r4, r5, r6]
260

261 262
    cfg_doc = json.dumps(cfg)

263 264
    # configuration of the Ext.data.Store for the source code
    cfg = Store()
LE GAC Renaud's avatar
LE GAC Renaud committed
265 266 267
    cfg.fields = [
        dict(name="code", type="string"),
        dict(name="source", type="string")]
268

269 270 271 272
    src = ""
    if plugin.app_git:
        src = "<em>git clone %s</em>" % plugin.app_git

LE GAC Renaud's avatar
LE GAC Renaud committed
273
    r1 = dict(code=request.application, source=src)
274

LE GAC Renaud's avatar
LE GAC Renaud committed
275 276 277 278
    r2 = dict(
        code="plugin_dbui",
        source="<em>git clone https://gitlab.in2p3.fr/w2pext/"
               "plugin_dbui.git</em>")
279

LE GAC Renaud's avatar
LE GAC Renaud committed
280 281 282
    r3 = dict(
        code="Web2py",
        source="<em>git clone https://github.com/web2py/web2py.git</em>")
283

LE GAC Renaud's avatar
LE GAC Renaud committed
284 285 286 287 288
    r4 = dict(
        code="Ext JS",
        source="<em>Download from the Ext JS web site. </em><br>"
               "<em>It is also available in the local directory: "
               "%s/static/plugin_extjs/src</em>" % request.application)
289

LE GAC Renaud's avatar
LE GAC Renaud committed
290 291 292
    r5 = dict(
        code="MathJax",
        source="<em>git clone http://github.com/mathjax/MathJax</em>")
293

LE GAC Renaud's avatar
LE GAC Renaud committed
294 295 296
    r6 = dict(
        code="Ace",
        source="<em>git clone git://github.com/ajaxorg/ace.git</em>")
297

298
    cfg.data = [r1, r2, r3, r4, r5, r6]
299

300
    cfg_src = json.dumps(cfg)
301 302

    return dict(cfg_doc=cfg_doc, cfg_src=cfg_src)
303 304


305 306 307 308 309 310 311 312 313 314 315 316 317
def documentations_list():
    """Documentations for users and developers display as list.
    Exploit documentations for the application located in:

        /static
            /docs
                /api
                /jsduck
                /latex
                /pdf
                /user

    Returns:
318 319
        dict:
            the configuration of the plugin dbui
320 321

    """
322 323
    plugin = PluginManager("dbui").dbui
    return dict(plugin=plugin)
324 325


326
def index():
legac's avatar
legac committed
327
    """Default Action to run the plugin
Renaud Le Gac's avatar
Renaud Le Gac committed
328
    Load compressed version of all libraries.
329

330
    EXAMPLES
331

332
        http://localhost:8000/myapp/plugin_dbui
333
        http://localhost:8000/myapp/plugin_dbui?debug
334
        http://localhost:8000/myapp/plugin_dbui?script=foo
335

Renaud Le Gac's avatar
Renaud Le Gac committed
336
    URL OPTIONS
337

338 339 340 341
        debug
            Load dynamically the javascript source code for all libraries.
            Active the debug mode on the server and client side.

Renaud Le Gac's avatar
Renaud Le Gac committed
342
        script
343
            run the javascript foo in the framework defined by the plugin.
Renaud Le Gac's avatar
Renaud Le Gac committed
344
            The scripts are stored in the directory defined by the plugin
345
            configuration app_script_dir.
346

347 348 349
    Returns:
        list of HTML string

350
    """
351
    from plugin_dbui import get_file_paths, get_script_path
352

353
    lst = []
354
    plugin = PluginManager("dbui").dbui
355
    plugins_paths = plugin.plugins_paths
356

357 358
    is_debug = "debug" in request.vars

359 360
    # css
    for plg in plugins_paths:
LE GAC Renaud's avatar
LE GAC Renaud committed
361 362 363
        path = plugins_paths[plg]["css"]
        if path is not None:
            lst.extend(get_file_paths(path, ext=".css"))
364

LE GAC Renaud's avatar
LE GAC Renaud committed
365
    lst.extend(get_file_paths(plugin.app_css, ext=".css"))
366

367 368 369 370
    # javascript libraries
    # depend on the debug mode either minified or full version
    key = ("debug" if is_debug else "libmin")

371
    for plg in plugins_paths:
LE GAC Renaud's avatar
LE GAC Renaud committed
372 373
        path = plugins_paths[plg][key]
        if path is not None:
374
            if plg == "extjs":
LE GAC Renaud's avatar
LE GAC Renaud committed
375
                lst.extend(get_file_paths(path, ext=".js", alpha=False))
376
            else:
LE GAC Renaud's avatar
LE GAC Renaud committed
377
                lst.extend(get_file_paths(path, ext=".js"))
378

379
    path = (plugin.app_debug if is_debug else plugin.app_libmin)
LE GAC Renaud's avatar
LE GAC Renaud committed
380
    lst.extend(get_file_paths(path, ext=".js"))
381

382 383
    # language
    for plg in plugins_paths:
LE GAC Renaud's avatar
LE GAC Renaud committed
384 385 386
        path = plugins_paths[plg]["lg"]
        if path is not None:
            lst.extend(get_file_paths(path, ext=".js"))
387

LE GAC Renaud's avatar
LE GAC Renaud committed
388
    lst.extend(get_file_paths(plugin.app_lg, ext=".js"))
389

390
    # URL for dbui configuration script
391 392
    path = (plugin.dbui_conf_debug if is_debug else plugin.dbui_conf)
    lst.append(path)
393

394
    # main script which can be defined in many different ways
395
    pscript = get_script_path(plugin)
396
    lst.append(pscript)
397

398 399
    # switch on/off the debug mode on the server side
    session.debug = is_debug
400

401 402 403 404
    # convert path into HTML
    fwlib = ""
    for el in lst:
        if el.endswith(".css"):
405 406
            fwlib += \
                '<link rel="stylesheet" type="text/css" href="%s"/>\n\t\t' % el
407
        else:
408
            fwlib += \
LE GAC Renaud's avatar
LE GAC Renaud committed
409
                '<script type="text/javascript" src="%s"></script>\n\t\t' % el
410 411

    return dict(fwlib=fwlib)
412 413


414
def latex2pdf():
415 416
    """Convert a LaTeX string into PDF.
    It is mandatory to define the request argument 'latex'.
417

418 419 420 421 422
    """
    from subprocess import call
    from tempfile import TemporaryFile
    from uuid import uuid4

423
    latex_string = request.vars.latex
424

425 426
    # create the latex file in  the private directory
    cwd = os.getcwd()
LE GAC Renaud's avatar
LE GAC Renaud committed
427
    os.chdir(os.path.join(request.folder, "private"))
428

429
    fn = str(uuid4())
430
    fi = open("%s.tex" % fn, "w")
431 432
    fi.write(latex_string)
    fi.close()
433

434
    # convert the latex file into PDF
435
    # run latex twice for longtable, ...
LE GAC Renaud's avatar
LE GAC Renaud committed
436
    cmd = ["pdflatex", "-interaction", "nonstopmode", "%s.tex" % fn]
437
    call(cmd, stdout=TemporaryFile())
438
    call(cmd, stdout=TemporaryFile())
439

440
    # clean latex processing
441
    for ext in ("aux", "log", "out", "tex"):
LE GAC Renaud's avatar
LE GAC Renaud committed
442
        f = "%s.%s" % (fn, ext)
443 444
        if os.path.exists(f):
            os.remove(f)
445

446
    # inform user when latex processing failed
LE GAC Renaud's avatar
LE GAC Renaud committed
447
    pdf = "%s.pdf" % fn
448
    if not os.path.exists(pdf):
LE GAC Renaud's avatar
LE GAC Renaud committed
449
        return "Empty PDF file since LaTeX processing failed."
450

451 452 453
    # go back to the web2py main directory
    os.chdir(cwd)

454 455 456
    # download the PDF file
    path_pdf = os.path.join(request.folder, "private", pdf)
    return response.stream(path_pdf)
457 458


459
def status():
legac's avatar
legac committed
460 461 462 463 464
    return dict(request=request, response=response, session=session)


def versions():
    """Return the versions of the different part of the code.
465

legac's avatar
legac committed
466
    """
LE GAC Renaud's avatar
LE GAC Renaud committed
467
    from plugin_dbui import get_versions, JSONEncoder, Store
468

469 470
    # the configuration for the Ext.data.Store
    cfg = Store()
471

LE GAC Renaud's avatar
LE GAC Renaud committed
472 473
    cfg.fields = [dict(name="code", type="string"),
                  dict(name="version", type="string")]
474

475
    cfg.data = get_versions()
476

LE GAC Renaud's avatar
LE GAC Renaud committed
477
    return dict(cfg_store=json.dumps(cfg, cls=JSONEncoder))