123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 |
- # -*- coding: utf-8 -*-
- # ##########################################################
- # ## make sure administrator is on localhost
- # ###########################################################
- import os
- import socket
- import datetime
- import copy
- import gluon.contenttype
- import gluon.fileutils
- try:
- import pygraphviz as pgv
- except ImportError:
- pgv = None
- is_gae = request.env.web2py_runtime_gae or False
- # ## critical --- make a copy of the environment
- global_env = copy.copy(globals())
- global_env['datetime'] = datetime
- http_host = request.env.http_host.split(':')[0]
- remote_addr = request.env.remote_addr
- try:
- hosts = (http_host, socket.gethostname(),
- socket.gethostbyname(http_host),
- '::1', '127.0.0.1', '::ffff:127.0.0.1')
- except:
- hosts = (http_host, )
- if request.is_https:
- session.secure()
- elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \
- (request.function != 'manage'):
- raise HTTP(200, T('appadmin is disabled because insecure channel'))
- if request.function == 'manage':
- if not 'auth' in globals() or not request.args:
- redirect(URL(request.controller, 'index'))
- manager_action = auth.settings.manager_actions.get(request.args(0), None)
- if manager_action is None and request.args(0) == 'auth':
- manager_action = dict(role=auth.settings.auth_manager_role,
- heading=T('Manage Access Control'),
- tables=[auth.table_user(),
- auth.table_group(),
- auth.table_permission()])
- manager_role = manager_action.get('role', None) if manager_action else None
- if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
- raise HTTP(403, "Not authorized")
- menu = False
- elif (request.application == 'admin' and not session.authorized) or \
- (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
- redirect(URL('admin', 'default', 'index',
- vars=dict(send=URL(args=request.args, vars=request.vars))))
- else:
- response.subtitle = T('Database Administration (appadmin)')
- menu = True
- ignore_rw = True
- response.view = 'appadmin.html'
- if menu:
- response.menu = [[T('design'), False, URL('admin', 'default', 'design',
- args=[request.application])], [T('db'), False,
- URL('index')], [T('state'), False,
- URL('state')], [T('cache'), False,
- URL('ccache')]]
- # ##########################################################
- # ## auxiliary functions
- # ###########################################################
- if False and request.tickets_db:
- from gluon.restricted import TicketStorage
- ts = TicketStorage()
- ts._get_table(request.tickets_db, ts.tablename, request.application)
- def get_databases(request):
- dbs = {}
- for (key, value) in global_env.items():
- try:
- cond = isinstance(value, GQLDB)
- except:
- cond = isinstance(value, SQLDB)
- if cond:
- dbs[key] = value
- return dbs
- databases = get_databases(None)
- def eval_in_global_env(text):
- exec ('_ret=%s' % text, {}, global_env)
- return global_env['_ret']
- def get_database(request):
- if request.args and request.args[0] in databases:
- return eval_in_global_env(request.args[0])
- else:
- session.flash = T('invalid request')
- redirect(URL('index'))
- def get_table(request):
- db = get_database(request)
- if len(request.args) > 1 and request.args[1] in db.tables:
- return (db, request.args[1])
- else:
- session.flash = T('invalid request')
- redirect(URL('index'))
- def get_query(request):
- try:
- return eval_in_global_env(request.vars.query)
- except Exception:
- return None
- def query_by_table_type(tablename, db, request=request):
- keyed = hasattr(db[tablename], '_primarykey')
- if keyed:
- firstkey = db[tablename][db[tablename]._primarykey[0]]
- cond = '>0'
- if firstkey.type in ['string', 'text']:
- cond = '!=""'
- qry = '%s.%s.%s%s' % (
- request.args[0], request.args[1], firstkey.name, cond)
- else:
- qry = '%s.%s.id>0' % tuple(request.args[:2])
- return qry
- # ##########################################################
- # ## list all databases and tables
- # ###########################################################
- def index():
- return dict(databases=databases)
- # ##########################################################
- # ## insert a new record
- # ###########################################################
- def insert():
- (db, table) = get_table(request)
- form = SQLFORM(db[table], ignore_rw=ignore_rw)
- if form.accepts(request.vars, session):
- response.flash = T('new record inserted')
- return dict(form=form, table=db[table])
- # ##########################################################
- # ## list all records in table and insert new record
- # ###########################################################
- def download():
- import os
- db = get_database(request)
- return response.download(request, db)
- def csv():
- import gluon.contenttype
- response.headers['Content-Type'] = \
- gluon.contenttype.contenttype('.csv')
- db = get_database(request)
- query = get_query(request)
- if not query:
- return None
- response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\
- % tuple(request.vars.query.split('.')[:2])
- return str(db(query, ignore_common_filters=True).select())
- def import_csv(table, file):
- table.import_from_csv_file(file)
- def select():
- import re
- db = get_database(request)
- dbname = request.args[0]
- try:
- is_imap = db._uri.startswith("imap://")
- except (KeyError, AttributeError, TypeError):
- is_imap = False
- regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
- if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'):
- regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)')
- if request.vars.query:
- match = regex.match(request.vars.query)
- if match:
- request.vars.query = '%s.%s.%s==%s' % (request.args[0],
- match.group('table'), match.group('field'),
- match.group('value'))
- else:
- request.vars.query = session.last_query
- query = get_query(request)
- if request.vars.start:
- start = int(request.vars.start)
- else:
- start = 0
- nrows = 0
- step = 100
- fields = []
- if is_imap:
- step = 3
-
- stop = start + step
- table = None
- rows = []
- orderby = request.vars.orderby
- if orderby:
- orderby = dbname + '.' + orderby
- if orderby == session.last_orderby:
- if orderby[0] == '~':
- orderby = orderby[1:]
- else:
- orderby = '~' + orderby
- session.last_orderby = orderby
- session.last_query = request.vars.query
- form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px',
- _name='query', _value=request.vars.query or '',
- requires=IS_NOT_EMPTY(
- error_message=T("Cannot be empty")))), TR(T('Update:'),
- INPUT(_name='update_check', _type='checkbox',
- value=False), INPUT(_style='width:400px',
- _name='update_fields', _value=request.vars.update_fields
- or '')), TR(T('Delete:'), INPUT(_name='delete_check',
- _class='delete', _type='checkbox', value=False), ''),
- TR('', '', INPUT(_type='submit', _value=T('submit')))),
- _action=URL(r=request, args=request.args))
- tb = None
- if form.accepts(request.vars, formname=None):
- regex = re.compile(request.args[0] + '\.(?P<table>\w+)\..+')
- match = regex.match(form.vars.query.strip())
- if match:
- table = match.group('table')
- try:
- nrows = db(query, ignore_common_filters=True).count()
- if form.vars.update_check and form.vars.update_fields:
- db(query, ignore_common_filters=True).update(
- **eval_in_global_env('dict(%s)' % form.vars.update_fields))
- response.flash = T('%s %%{row} updated', nrows)
- elif form.vars.delete_check:
- db(query, ignore_common_filters=True).delete()
- response.flash = T('%s %%{row} deleted', nrows)
- nrows = db(query, ignore_common_filters=True).count()
- if is_imap:
- fields = [db[table][name] for name in
- ("id", "uid", "created", "to",
- "sender", "subject")]
- if orderby:
- rows = db(query, ignore_common_filters=True).select(
- *fields, limitby=(start, stop),
- orderby=eval_in_global_env(orderby))
- else:
- rows = db(query, ignore_common_filters=True).select(
- *fields, limitby=(start, stop))
- except Exception, e:
- import traceback
- tb = traceback.format_exc()
- (rows, nrows) = ([], 0)
- response.flash = DIV(T('Invalid Query'), PRE(str(e)))
- # begin handle upload csv
- csv_table = table or request.vars.table
- if csv_table:
- formcsv = FORM(str(T('or import from csv file')) + " ",
- INPUT(_type='file', _name='csvfile'),
- INPUT(_type='hidden', _value=csv_table, _name='table'),
- INPUT(_type='submit', _value=T('import')))
- else:
- formcsv = None
- if formcsv and formcsv.process().accepted:
- try:
- import_csv(db[request.vars.table],
- request.vars.csvfile.file)
- response.flash = T('data uploaded')
- except Exception, e:
- response.flash = DIV(T('unable to parse csv file'), PRE(str(e)))
- # end handle upload csv
- return dict(
- form=form,
- table=table,
- start=start,
- stop=stop,
- step=step,
- nrows=nrows,
- rows=rows,
- query=request.vars.query,
- formcsv=formcsv,
- tb=tb
- )
- # ##########################################################
- # ## edit delete one record
- # ###########################################################
- def update():
- (db, table) = get_table(request)
- keyed = hasattr(db[table], '_primarykey')
- record = None
- db[table]._common_filter = None
- if keyed:
- key = [f for f in request.vars if f in db[table]._primarykey]
- if key:
- record = db(db[table][key[0]] == request.vars[key[
- 0]]).select().first()
- else:
- record = db(db[table].id == request.args(
- 2)).select().first()
- if not record:
- qry = query_by_table_type(table, db)
- session.flash = T('record does not exist')
- redirect(URL('select', args=request.args[:1],
- vars=dict(query=qry)))
- if keyed:
- for k in db[table]._primarykey:
- db[table][k].writable = False
- form = SQLFORM(
- db[table], record, deletable=True, delete_label=T('Check to delete'),
- ignore_rw=ignore_rw and not keyed,
- linkto=URL('select',
- args=request.args[:1]), upload=URL(r=request,
- f='download', args=request.args[:1]))
- if form.accepts(request.vars, session):
- session.flash = T('done!')
- qry = query_by_table_type(table, db)
- redirect(URL('select', args=request.args[:1],
- vars=dict(query=qry)))
- return dict(form=form, table=db[table])
- # ##########################################################
- # ## get global variables
- # ###########################################################
- def state():
- return dict()
- def ccache():
- if is_gae:
- form = FORM(
- P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")))
- else:
- cache.ram.initialize()
- cache.disk.initialize()
- form = FORM(
- P(TAG.BUTTON(
- T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")),
- P(TAG.BUTTON(
- T("Clear RAM"), _type="submit", _name="ram", _value="ram")),
- P(TAG.BUTTON(
- T("Clear DISK"), _type="submit", _name="disk", _value="disk")),
- )
- if form.accepts(request.vars, session):
- session.flash = ""
- if is_gae:
- if request.vars.yes:
- cache.ram.clear()
- session.flash += T("Cache Cleared")
- else:
- clear_ram = False
- clear_disk = False
- if request.vars.yes:
- clear_ram = clear_disk = True
- if request.vars.ram:
- clear_ram = True
- if request.vars.disk:
- clear_disk = True
- if clear_ram:
- cache.ram.clear()
- session.flash += T("Ram Cleared")
- if clear_disk:
- cache.disk.clear()
- session.flash += T("Disk Cleared")
- redirect(URL(r=request))
- try:
- from guppy import hpy
- hp = hpy()
- except ImportError:
- hp = False
- import shelve
- import os
- import copy
- import time
- import math
- from gluon import portalocker
- ram = {
- 'entries': 0,
- 'bytes': 0,
- 'objects': 0,
- 'hits': 0,
- 'misses': 0,
- 'ratio': 0,
- 'oldest': time.time(),
- 'keys': []
- }
-
- disk = copy.copy(ram)
- total = copy.copy(ram)
- disk['keys'] = []
- total['keys'] = []
- def GetInHMS(seconds):
- hours = math.floor(seconds / 3600)
- seconds -= hours * 3600
- minutes = math.floor(seconds / 60)
- seconds -= minutes * 60
- seconds = math.floor(seconds)
- return (hours, minutes, seconds)
- if is_gae:
- gae_stats = cache.ram.client.get_stats()
- try:
- gae_stats['ratio'] = ((gae_stats['hits'] * 100) /
- (gae_stats['hits'] + gae_stats['misses']))
- except ZeroDivisionError:
- gae_stats['ratio'] = T("?")
- gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
- total.update(gae_stats)
- else:
- for key, value in cache.ram.storage.iteritems():
- if isinstance(value, dict):
- ram['hits'] = value['hit_total'] - value['misses']
- ram['misses'] = value['misses']
- try:
- ram['ratio'] = ram['hits'] * 100 / value['hit_total']
- except (KeyError, ZeroDivisionError):
- ram['ratio'] = 0
- else:
- if hp:
- ram['bytes'] += hp.iso(value[1]).size
- ram['objects'] += hp.iso(value[1]).count
- ram['entries'] += 1
- if value[0] < ram['oldest']:
- ram['oldest'] = value[0]
- ram['keys'].append((key, GetInHMS(time.time() - value[0])))
- for key in cache.disk.storage:
- value = cache.disk.storage[key]
- if isinstance(value, dict):
- disk['hits'] = value['hit_total'] - value['misses']
- disk['misses'] = value['misses']
- try:
- disk['ratio'] = disk['hits'] * 100 / value['hit_total']
- except (KeyError, ZeroDivisionError):
- disk['ratio'] = 0
- else:
- if hp:
- disk['bytes'] += hp.iso(value[1]).size
- disk['objects'] += hp.iso(value[1]).count
- disk['entries'] += 1
- if value[0] < disk['oldest']:
- disk['oldest'] = value[0]
- disk['keys'].append((key, GetInHMS(time.time() - value[0])))
- ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
- ram_keys.remove('ratio')
- ram_keys.remove('oldest')
- for key in ram_keys:
- total[key] = ram[key] + disk[key]
-
- try:
- total['ratio'] = total['hits'] * 100 / (total['hits'] +
- total['misses'])
- except (KeyError, ZeroDivisionError):
- total['ratio'] = 0
- if disk['oldest'] < ram['oldest']:
- total['oldest'] = disk['oldest']
- else:
- total['oldest'] = ram['oldest']
- ram['oldest'] = GetInHMS(time.time() - ram['oldest'])
- disk['oldest'] = GetInHMS(time.time() - disk['oldest'])
- total['oldest'] = GetInHMS(time.time() - total['oldest'])
- def key_table(keys):
- return TABLE(
- TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))),
- *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys],
- **dict(_class='cache-keys',
- _style="border-collapse: separate; border-spacing: .5em;"))
- if not is_gae:
- ram['keys'] = key_table(ram['keys'])
- disk['keys'] = key_table(disk['keys'])
- total['keys'] = key_table(total['keys'])
- return dict(form=form, total=total,
- ram=ram, disk=disk, object_stats=hp != False)
- def table_template(table):
- from gluon.html import TR, TD, TABLE, TAG
- def FONT(*args, **kwargs):
- return TAG.font(*args, **kwargs)
- def types(field):
- f_type = field.type
- if not isinstance(f_type,str):
- return ' '
- elif f_type == 'string':
- return field.length
- elif f_type == 'id':
- return B('pk')
- elif f_type.startswith('reference') or \
- f_type.startswith('list:reference'):
- return B('fk')
- else:
- return ' '
- # This is horribe HTML but the only one graphiz understands
- rows = []
- cellpadding = 4
- color = "#000000"
- bgcolor = "#FFFFFF"
- face = "Helvetica"
- face_bold = "Helvetica Bold"
- border = 0
- rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
- _colspan=3, _cellpadding=cellpadding,
- _align="center", _bgcolor=color)))
- for row in db[table]:
- rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold),
- _align="left", _cellpadding=cellpadding,
- _border=border),
- TD(FONT(row.type, _color=color, _face=face),
- _align="left", _cellpadding=cellpadding,
- _border=border),
- TD(FONT(types(row), _color=color, _face=face),
- _align="center", _cellpadding=cellpadding,
- _border=border)))
- return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1,
- _cellborder=0, _cellspacing=0)
- ).xml()
- def bg_graph_model():
- graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
- subgraphs = dict()
- for tablename in db.tables:
- if hasattr(db[tablename],'_meta_graphmodel'):
- meta_graphmodel = db[tablename]._meta_graphmodel
- else:
- meta_graphmodel = dict(group=request.application, color='#ECECEC')
- group = meta_graphmodel['group'].replace(' ', '')
- if not subgraphs.has_key(group):
- subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
- subgraphs[group]['tables'].append(tablename)
- graph.add_node(tablename, name=tablename, shape='plaintext',
- label=table_template(tablename))
- for n, key in enumerate(subgraphs.iterkeys()):
- graph.subgraph(nbunch=subgraphs[key]['tables'],
- name='cluster%d' % n,
- style='filled',
- color=subgraphs[key]['meta']['color'],
- label=subgraphs[key]['meta']['group'])
- for tablename in db.tables:
- for field in db[tablename]:
- f_type = field.type
- if isinstance(f_type,str) and (
- f_type.startswith('reference') or
- f_type.startswith('list:reference')):
- referenced_table = f_type.split()[1].split('.')[0]
- n1 = graph.get_node(tablename)
- n2 = graph.get_node(referenced_table)
- graph.add_edge(n1, n2, color="#4C4C4C", label='')
- graph.layout()
- if not request.args:
- response.headers['Content-Type'] = 'image/png'
- return graph.draw(format='png', prog='dot')
- else:
- response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
- if request.args(0) == 'dot':
- return graph.string()
- else:
- return graph.draw(format=request.args(0), prog='dot')
- def graph_model():
- return dict(databases=databases, pgv=pgv)
- def manage():
- tables = manager_action['tables']
- if isinstance(tables[0], str):
- db = manager_action.get('db', auth.db)
- db = globals()[db] if isinstance(db, str) else db
- tables = [db[table] for table in tables]
- if request.args(0) == 'auth':
- auth.table_user()._plural = T('Users')
- auth.table_group()._plural = T('Roles')
- auth.table_membership()._plural = T('Memberships')
- auth.table_permission()._plural = T('Permissions')
- if request.extension != 'load':
- return dict(heading=manager_action.get('heading',
- T('Manage %(action)s') % dict(action=request.args(0).replace('_', ' ').title())),
- tablenames=[table._tablename for table in tables],
- labels=[table._plural.title() for table in tables])
- table = tables[request.args(1, cast=int)]
- formname = '%s_grid' % table._tablename
- linked_tables = orderby = None
- if request.args(0) == 'auth':
- auth.table_group()._id.readable = \
- auth.table_membership()._id.readable = \
- auth.table_permission()._id.readable = False
- auth.table_membership().user_id.label = T('User')
- auth.table_membership().group_id.label = T('Role')
- auth.table_permission().group_id.label = T('Role')
- auth.table_permission().name.label = T('Permission')
- if table == auth.table_user():
- linked_tables=[auth.settings.table_membership_name]
- elif table == auth.table_group():
- orderby = 'role' if not request.args(3) or '.group_id' not in request.args(3) else None
- elif table == auth.table_permission():
- orderby = 'group_id'
- kwargs = dict(user_signature=True, maxtextlength=1000,
- orderby=orderby, linked_tables=linked_tables)
- smartgrid_args = manager_action.get('smartgrid_args', {})
- kwargs.update(**smartgrid_args.get('DEFAULT', {}))
- kwargs.update(**smartgrid_args.get(table._tablename, {}))
- grid = SQLFORM.smartgrid(table, args=request.args[:2], formname=formname, **kwargs)
- return grid
- def hooks():
- import functools
- import inspect
- list_op=['_%s_%s' %(h,m) for h in ['before', 'after'] for m in ['insert','update','delete']]
- tables=[]
- with_build_it=False
- for db_str in sorted(databases):
- db = databases[db_str]
- for t in db.tables:
- method_hooks=[]
- for op in list_op:
- functions = []
- for f in getattr(db[t], op):
- if hasattr(f, '__call__'):
- try:
- if isinstance(f, (functools.partial)):
- f = f.func
- filename = inspect.getsourcefile(f)
- details = {'funcname':f.__name__,
- 'filename':filename[len(request.folder):] if request.folder in filename else None,
- 'lineno': inspect.getsourcelines(f)[1]}
- if details['filename']: # Built in functions as delete_uploaded_files are not editable
- details['url'] = URL(a='admin',c='default',f='edit', args=[request['application'], details['filename']],vars={'lineno':details['lineno']})
- if details['filename'] or with_build_it:
- functions.append(details)
- # compiled app and windows build don't support code inspection
- except:
- pass
- if len(functions):
- method_hooks.append({'name':op, 'functions':functions})
- if len(method_hooks):
- tables.append({'name':"%s.%s" % (db_str,t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks})
- # Render
- ul_main = UL(_class='nav nav-list')
- for t in tables:
- ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug']))
- ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none')
- for op in t['method_hooks']:
- ul_t.append(LI (op['name']))
- 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']]))
- ul_main.append(ul_t)
- return ul_main
|