source: OpenRLabs-Git/web2py/applications/rlabs/controllers/appadmin.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100644
File size: 25.0 KB
Line 
1# -*- coding: utf-8 -*-
2
3# ##########################################################
4# ## make sure administrator is on localhost
5# ###########################################################
6
7import copy
8import datetime
9import os
10import socket
11
12from gluon._compat import iteritems
13import gluon.contenttype
14import gluon.fileutils
15
16
17is_gae = request.env.web2py_runtime_gae or False
18
19# ## critical --- make a copy of the environment
20
21global_env = copy.copy(globals())
22global_env['datetime'] = datetime
23
24http_host = request.env.http_host.split(':')[0]
25remote_addr = request.env.remote_addr
26try:
27    hosts = (http_host, socket.gethostname(),
28             socket.gethostbyname(http_host),
29             '::1', '127.0.0.1', '::ffff:127.0.0.1')
30except:
31    hosts = (http_host, )
32
33if request.is_https:
34    session.secure()
35elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \
36    (request.function != 'manage'):
37    raise HTTP(200, T('appadmin is disabled because insecure channel'))
38
39if request.function == 'manage':
40    if not 'auth' in globals() or not request.args:
41        redirect(URL(request.controller, 'index'))
42    manager_action = auth.settings.manager_actions.get(request.args(0), None)
43    if manager_action is None and request.args(0) == 'auth':
44        manager_action = dict(role=auth.settings.auth_manager_role,
45                              heading=T('Manage Access Control'),
46                              tables=[auth.table_user(),
47                                      auth.table_group(),
48                                      auth.table_permission()])
49    manager_role = manager_action.get('role', None) if manager_action else None
50    if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
51        raise HTTP(403, "Not authorized")
52    menu = False
53elif (request.application == 'admin' and not session.authorized) or \
54        (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
55    redirect(URL('admin', 'default', 'index',
56                 vars=dict(send=URL(args=request.args, vars=request.vars))))
57else:
58    response.subtitle = T('Database Administration (appadmin)')
59    menu = True
60
61ignore_rw = True
62response.view = 'appadmin.html'
63if menu:
64    response.menu = [[T('design'), False, URL('admin', 'default', 'design',
65                 args=[request.application])], [T('db'), False,
66                 URL('index')], [T('state'), False,
67                 URL('state')], [T('cache'), False,
68                 URL('ccache')]]
69
70# ##########################################################
71# ## auxiliary functions
72# ###########################################################
73
74if False and request.tickets_db:
75    from gluon.restricted import TicketStorage
76    ts = TicketStorage()
77    ts._get_table(request.tickets_db, ts.tablename, request.application)
78
79def get_databases(request):
80    dbs = {}
81    for (key, value) in global_env.items():
82        try:
83            cond = isinstance(value, GQLDB)
84        except:
85            cond = isinstance(value, SQLDB)
86        if cond:
87            dbs[key] = value
88    return dbs
89
90databases = get_databases(None)
91
92def eval_in_global_env(text):
93    exec ('_ret=%s' % text, {}, global_env)
94    return global_env['_ret']
95
96
97def get_database(request):
98    if request.args and request.args[0] in databases:
99        return eval_in_global_env(request.args[0])
100    else:
101        session.flash = T('invalid request')
102        redirect(URL('index'))
103
104def get_table(request):
105    db = get_database(request)
106    if len(request.args) > 1 and request.args[1] in db.tables:
107        return (db, request.args[1])
108    else:
109        session.flash = T('invalid request')
110        redirect(URL('index'))
111
112
113def get_query(request):
114    try:
115        return eval_in_global_env(request.vars.query)
116    except Exception:
117        return None
118
119
120def query_by_table_type(tablename, db, request=request):
121    keyed = hasattr(db[tablename], '_primarykey')
122    if keyed:
123        firstkey = db[tablename][db[tablename]._primarykey[0]]
124        cond = '>0'
125        if firstkey.type in ['string', 'text']:
126            cond = '!=""'
127        qry = '%s.%s.%s%s' % (
128            request.args[0], request.args[1], firstkey.name, cond)
129    else:
130        qry = '%s.%s.id>0' % tuple(request.args[:2])
131    return qry
132
133
134# ##########################################################
135# ## list all databases and tables
136# ###########################################################
137def index():
138    return dict(databases=databases)
139
140
141# ##########################################################
142# ## insert a new record
143# ###########################################################
144
145
146def insert():
147    (db, table) = get_table(request)
148    form = SQLFORM(db[table], ignore_rw=ignore_rw)
149    if form.accepts(request.vars, session):
150        response.flash = T('new record inserted')
151    return dict(form=form, table=db[table])
152
153
154# ##########################################################
155# ## list all records in table and insert new record
156# ###########################################################
157
158
159def download():
160    import os
161    db = get_database(request)
162    return response.download(request, db)
163
164
165def csv():
166    import gluon.contenttype
167    response.headers['Content-Type'] = \
168        gluon.contenttype.contenttype('.csv')
169    db = get_database(request)
170    query = get_query(request)
171    if not query:
172        return None
173    response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\
174        % tuple(request.vars.query.split('.')[:2])
175    return str(db(query, ignore_common_filters=True).select())
176
177
178def import_csv(table, file):
179    table.import_from_csv_file(file)
180
181
182def select():
183    import re
184    db = get_database(request)
185    dbname = request.args[0]
186    try:
187        is_imap = db._uri.startswith("imap://")
188    except (KeyError, AttributeError, TypeError):
189        is_imap = False
190    regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
191    if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'):
192        regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)')
193    if request.vars.query:
194        match = regex.match(request.vars.query)
195        if match:
196            request.vars.query = '%s.%s.%s==%s' % (request.args[0],
197                                                   match.group('table'), match.group('field'),
198                                                   match.group('value'))
199    else:
200        request.vars.query = session.last_query
201    query = get_query(request)
202    if request.vars.start:
203        start = int(request.vars.start)
204    else:
205        start = 0
206    nrows = 0
207
208    step = 100
209    fields = []
210
211    if is_imap:
212        step = 3
213
214    stop = start + step
215
216    table = None
217    rows = []
218    orderby = request.vars.orderby
219    if orderby:
220        orderby = dbname + '.' + orderby
221        if orderby == session.last_orderby:
222            if orderby[0] == '~':
223                orderby = orderby[1:]
224            else:
225                orderby = '~' + orderby
226    session.last_orderby = orderby
227    session.last_query = request.vars.query
228    form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px',
229                _name='query', _value=request.vars.query or '', _class="form-control",
230                requires=IS_NOT_EMPTY(
231                    error_message=T("Cannot be empty")))), TR(T('Update:'),
232                INPUT(_name='update_check', _type='checkbox',
233                value=False), INPUT(_style='width:400px',
234                _name='update_fields', _value=request.vars.update_fields
235                                    or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check',
236                _class='delete', _type='checkbox', value=False), ''),
237                TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))),
238                _action=URL(r=request, args=request.args))
239
240    tb = None
241    if form.accepts(request.vars, formname=None):
242        regex = re.compile(request.args[0] + r'\.(?P<table>\w+)\..+')
243        match = regex.match(form.vars.query.strip())
244        if match:
245            table = match.group('table')
246        try:
247            nrows = db(query, ignore_common_filters=True).count()
248            if form.vars.update_check and form.vars.update_fields:
249                db(query, ignore_common_filters=True).update(
250                    **eval_in_global_env('dict(%s)' % form.vars.update_fields))
251                response.flash = T('%s %%{row} updated', nrows)
252            elif form.vars.delete_check:
253                db(query, ignore_common_filters=True).delete()
254                response.flash = T('%s %%{row} deleted', nrows)
255            nrows = db(query, ignore_common_filters=True).count()
256
257            if is_imap:
258                fields = [db[table][name] for name in
259                    ("id", "uid", "created", "to",
260                     "sender", "subject")]
261            if orderby:
262                rows = db(query, ignore_common_filters=True).select(
263                              *fields, limitby=(start, stop),
264                              orderby=eval_in_global_env(orderby))
265            else:
266                rows = db(query, ignore_common_filters=True).select(
267                    *fields, limitby=(start, stop))
268        except Exception as e:
269            import traceback
270            tb = traceback.format_exc()
271            (rows, nrows) = ([], 0)
272            response.flash = DIV(T('Invalid Query'), PRE(str(e)))
273    # begin handle upload csv
274    csv_table = table or request.vars.table
275    if csv_table:
276        formcsv = FORM(str(T('or import from csv file')) + " ",
277                       INPUT(_type='file', _name='csvfile'),
278                       INPUT(_type='hidden', _value=csv_table, _name='table'),
279                       INPUT(_type='submit', _value=T('import'), _class="btn btn-primary"))
280    else:
281        formcsv = None
282    if formcsv and formcsv.process().accepted:
283        try:
284            import_csv(db[request.vars.table],
285                       request.vars.csvfile.file)
286            response.flash = T('data uploaded')
287        except Exception as e:
288            response.flash = DIV(T('unable to parse csv file'), PRE(str(e)))
289    # end handle upload csv
290
291    return dict(
292        form=form,
293        table=table,
294        start=start,
295        stop=stop,
296        step=step,
297        nrows=nrows,
298        rows=rows,
299        query=request.vars.query,
300        formcsv=formcsv,
301        tb=tb
302    )
303
304
305# ##########################################################
306# ## edit delete one record
307# ###########################################################
308
309
310def update():
311    (db, table) = get_table(request)
312    keyed = hasattr(db[table], '_primarykey')
313    record = None
314    db[table]._common_filter = None
315    if keyed:
316        key = [f for f in request.vars if f in db[table]._primarykey]
317        if key:
318            record = db(db[table][key[0]] == request.vars[key[
319                        0]]).select().first()
320    else:
321        record = db(db[table].id == request.args(
322            2)).select().first()
323
324    if not record:
325        qry = query_by_table_type(table, db)
326        session.flash = T('record does not exist')
327        redirect(URL('select', args=request.args[:1],
328                     vars=dict(query=qry)))
329
330    if keyed:
331        for k in db[table]._primarykey:
332            db[table][k].writable = False
333   
334    form = SQLFORM(
335        db[table], record, deletable=True, delete_label=T('Check to delete'),
336        ignore_rw=ignore_rw and not keyed,
337        linkto=URL('select',
338                   args=request.args[:1]), upload=URL(r=request,
339                                                      f='download', args=request.args[:1]))
340
341    if form.accepts(request.vars, session):
342        session.flash = T('done!')
343        qry = query_by_table_type(table, db)
344        redirect(URL('select', args=request.args[:1],
345                 vars=dict(query=qry)))
346    return dict(form=form, table=db[table])
347
348
349# ##########################################################
350# ## get global variables
351# ###########################################################
352
353
354def state():
355    return dict()
356
357
358def ccache():
359    if is_gae:
360        form = FORM(
361            P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")))
362    else:
363        cache.ram.initialize()
364        cache.disk.initialize()
365
366        form = FORM(
367            P(TAG.BUTTON(
368                T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")),
369            P(TAG.BUTTON(
370                T("Clear RAM"), _type="submit", _name="ram", _value="ram")),
371            P(TAG.BUTTON(
372                T("Clear DISK"), _type="submit", _name="disk", _value="disk")),
373        )
374
375    if form.accepts(request.vars, session):
376        session.flash = ""
377        if is_gae:
378            if request.vars.yes:
379                cache.ram.clear()
380                session.flash += T("Cache Cleared")
381        else:
382            clear_ram = False
383            clear_disk = False
384            if request.vars.yes:
385                clear_ram = clear_disk = True
386            if request.vars.ram:
387                clear_ram = True
388            if request.vars.disk:
389                clear_disk = True
390            if clear_ram:
391                cache.ram.clear()
392                session.flash += T("Ram Cleared")
393            if clear_disk:
394                cache.disk.clear()
395                session.flash += T("Disk Cleared")
396        redirect(URL(r=request))
397
398    try:
399        from pympler.asizeof import asizeof
400    except ImportError:
401        asizeof = False
402
403    import shelve
404    import os
405    import copy
406    import time
407    import math
408    from pydal.contrib import portalocker
409
410    ram = {
411        'entries': 0,
412        'bytes': 0,
413        'objects': 0,
414        'hits': 0,
415        'misses': 0,
416        'ratio': 0,
417        'oldest': time.time(),
418        'keys': []
419    }
420
421    disk = copy.copy(ram)
422    total = copy.copy(ram)
423    disk['keys'] = []
424    total['keys'] = []
425
426    def GetInHMS(seconds):
427        hours = math.floor(seconds / 3600)
428        seconds -= hours * 3600
429        minutes = math.floor(seconds / 60)
430        seconds -= minutes * 60
431        seconds = math.floor(seconds)
432
433        return (hours, minutes, seconds)
434
435    if is_gae:
436        gae_stats = cache.ram.client.get_stats()
437        try:
438            gae_stats['ratio'] = ((gae_stats['hits'] * 100) /
439                (gae_stats['hits'] + gae_stats['misses']))
440        except ZeroDivisionError:
441            gae_stats['ratio'] = T("?")
442        gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
443        total.update(gae_stats)
444    else:
445        # get ram stats directly from the cache object
446        ram_stats = cache.ram.stats[request.application]
447        ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
448        ram['misses'] = ram_stats['misses']
449        try:
450            ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
451        except (KeyError, ZeroDivisionError):
452            ram['ratio'] = 0
453
454        for key, value in iteritems(cache.ram.storage):
455            if asizeof:
456                ram['bytes'] += asizeof(value[1])
457                ram['objects'] += 1
458            ram['entries'] += 1
459            if value[0] < ram['oldest']:
460                ram['oldest'] = value[0]
461            ram['keys'].append((key, GetInHMS(time.time() - value[0])))
462
463        for key in cache.disk.storage:
464            value = cache.disk.storage[key]
465            if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
466                disk['hits'] = value[1]['hit_total'] - value[1]['misses']
467                disk['misses'] = value[1]['misses']
468                try:
469                    disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
470                except (KeyError, ZeroDivisionError):
471                    disk['ratio'] = 0
472            else:
473                if asizeof:
474                    disk['bytes'] += asizeof(value[1])
475                    disk['objects'] += 1
476                disk['entries'] += 1
477                if value[0] < disk['oldest']:
478                    disk['oldest'] = value[0]
479                disk['keys'].append((key, GetInHMS(time.time() - value[0])))
480
481        ram_keys = list(ram) # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
482        ram_keys.remove('ratio')
483        ram_keys.remove('oldest')
484        for key in ram_keys:
485            total[key] = ram[key] + disk[key]
486
487        try:
488            total['ratio'] = total['hits'] * 100 / (total['hits'] +
489                                                total['misses'])
490        except (KeyError, ZeroDivisionError):
491            total['ratio'] = 0
492
493        if disk['oldest'] < ram['oldest']:
494            total['oldest'] = disk['oldest']
495        else:
496            total['oldest'] = ram['oldest']
497
498        ram['oldest'] = GetInHMS(time.time() - ram['oldest'])
499        disk['oldest'] = GetInHMS(time.time() - disk['oldest'])
500        total['oldest'] = GetInHMS(time.time() - total['oldest'])
501
502    def key_table(keys):
503        return TABLE(
504            TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))),
505            *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys],
506            **dict(_class='cache-keys',
507                   _style="border-collapse: separate; border-spacing: .5em;"))
508
509    if not is_gae:
510        ram['keys'] = key_table(ram['keys'])
511        disk['keys'] = key_table(disk['keys'])
512        total['keys'] = key_table(total['keys'])
513
514    return dict(form=form, total=total,
515                ram=ram, disk=disk, object_stats=asizeof != False)
516
517
518def table_template(table):
519    from gluon.html import TR, TD, TABLE, TAG
520
521    def FONT(*args, **kwargs):
522        return TAG.font(*args, **kwargs)
523
524    def types(field):
525        f_type = field.type
526        if not isinstance(f_type,str):
527            return ' '
528        elif f_type == 'string':
529            return field.length
530        elif f_type == 'id':
531            return B('pk')
532        elif f_type.startswith('reference') or \
533                f_type.startswith('list:reference'):
534            return B('fk')
535        else:
536            return ' '
537
538    # This is horribe HTML but the only one graphiz understands
539    rows = []
540    cellpadding = 4
541    color = "#000000"
542    bgcolor = "#FFFFFF"
543    face = "Helvetica"
544    face_bold = "Helvetica Bold"
545    border = 0
546
547    rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
548                           _colspan=3, _cellpadding=cellpadding,
549                           _align="center", _bgcolor=color)))
550    for row in db[table]:
551        rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold),
552                              _align="left", _cellpadding=cellpadding,
553                              _border=border),
554                       TD(FONT(row.type, _color=color, _face=face),
555                               _align="left", _cellpadding=cellpadding,
556                               _border=border),
557                       TD(FONT(types(row), _color=color, _face=face),
558                               _align="center", _cellpadding=cellpadding,
559                               _border=border)))
560    return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1,
561                                          _cellborder=0, _cellspacing=0)
562                             ).xml()
563
564def manage():   
565    tables = manager_action['tables']
566    if isinstance(tables[0], str):
567        db = manager_action.get('db', auth.db)
568        db = globals()[db] if isinstance(db, str) else db
569        tables = [db[table] for table in tables]
570    if request.args(0) == 'auth':
571        auth.table_user()._plural = T('Users')
572        auth.table_group()._plural = T('Roles')
573        auth.table_membership()._plural = T('Memberships')
574        auth.table_permission()._plural = T('Permissions')
575    if request.extension != 'load':
576        return dict(heading=manager_action.get('heading',
577                    T('Manage %(action)s') % dict(action=request.args(0).replace('_', ' ').title())),
578                    tablenames=[table._tablename for table in tables],
579                    labels=[table._plural.title() for table in tables])
580
581    table = tables[request.args(1, cast=int)]
582    formname = '%s_grid' % table._tablename
583    linked_tables = orderby = None
584    if request.args(0) == 'auth':
585        auth.table_group()._id.readable = \
586        auth.table_membership()._id.readable = \
587        auth.table_permission()._id.readable = False
588        auth.table_membership().user_id.label = T('User')
589        auth.table_membership().group_id.label = T('Role')
590        auth.table_permission().group_id.label = T('Role')
591        auth.table_permission().name.label = T('Permission')
592        if table == auth.table_user():
593            linked_tables = [auth.settings.table_membership_name]
594        elif table == auth.table_group():
595            orderby = 'role' if not request.args(3) or '.group_id' not in request.args(3) else None
596        elif table == auth.table_permission():
597            orderby = 'group_id'
598    kwargs = dict(user_signature=True, maxtextlength=1000,
599                  orderby=orderby, linked_tables=linked_tables)
600    smartgrid_args = manager_action.get('smartgrid_args', {})
601    kwargs.update(**smartgrid_args.get('DEFAULT', {}))
602    kwargs.update(**smartgrid_args.get(table._tablename, {}))
603    grid = SQLFORM.smartgrid(table, args=request.args[:2], formname=formname, **kwargs)
604    return grid
605
606def hooks():
607    import functools
608    import inspect
609    list_op = ['_%s_%s' %(h,m) for h in ['before', 'after'] for m in ['insert','update','delete']]
610    tables = []
611    with_build_it = False
612    for db_str in sorted(databases):
613        db = databases[db_str]
614        for t in db.tables:
615            method_hooks = []
616            for op in list_op:
617                functions = []
618                for f in getattr(db[t], op):
619                    if hasattr(f, '__call__'):
620                        try:
621                            if isinstance(f, (functools.partial)):
622                                f = f.func
623                            filename = inspect.getsourcefile(f)
624                            details = {'funcname':f.__name__,
625                                       'filename':filename[len(request.folder):] if request.folder in filename else None,
626                                       'lineno': inspect.getsourcelines(f)[1]}
627                            if details['filename']: # Built in functions as delete_uploaded_files are not editable
628                                details['url'] = URL(a='admin',c='default',f='edit', args=[request['application'], details['filename']],vars={'lineno':details['lineno']})
629                            if details['filename'] or with_build_it:
630                                functions.append(details)
631                        # compiled app and windows build don't support code inspection
632                        except:
633                            pass
634                if len(functions):
635                    method_hooks.append({'name': op, 'functions':functions})
636            if len(method_hooks):
637                tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks})
638    # Render
639    ul_main = UL(_class='nav nav-list')
640    for t in tables:
641        ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug']))
642        ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none')
643        for op in t['method_hooks']:
644            ul_t.append(LI(op['name']))
645            ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
646        ul_main.append(ul_t)
647    return ul_main
648
649
650# ##########################################################
651# d3 based model visualizations
652# ###########################################################
653
654def d3_graph_model():
655    """ See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
656    and also the app_admin bg_graph_model function
657
658    Create a list of table dicts, called "nodes"
659    """
660
661    nodes = []
662    links = []
663
664    for database in databases:
665        db = eval_in_global_env(database)
666        for tablename in db.tables:
667            fields = []
668            for field in db[tablename]:
669                f_type = field.type
670                if not isinstance(f_type, str):
671                    disp = ' '
672                elif f_type == 'string':
673                    disp = field.length
674                elif f_type == 'id':
675                    disp = "PK"
676                elif f_type.startswith('reference') or \
677                    f_type.startswith('list:reference'):
678                    disp = "FK"
679                else:
680                    disp = ' '
681                fields.append(dict(name=field.name, type=field.type, disp=disp))
682
683                if isinstance(f_type, str) and (
684                    f_type.startswith('reference') or
685                    f_type.startswith('list:reference')):
686                    referenced_table = f_type.split()[1].split('.')[0]
687
688                    links.append(dict(source=tablename, target = referenced_table))
689
690            nodes.append(dict(name=tablename, type="table", fields = fields))
691
692    # d3 v4 allows individual modules to be specified.  The complete d3 library is included below.
693    response.files.append(URL('admin','static','js/d3.min.js'))
694    response.files.append(URL('admin','static','js/d3_graph.js'))
695    return dict(databases=databases, nodes=nodes, links=links)
Note: See TracBrowser for help on using the repository browser.