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