1 | # -*- coding: utf-8 -*- |
---|
2 | |
---|
3 | # ########################################################## |
---|
4 | # ## make sure administrator is on localhost |
---|
5 | # ########################################################### |
---|
6 | |
---|
7 | import os |
---|
8 | import socket |
---|
9 | import datetime |
---|
10 | import copy |
---|
11 | import gluon.contenttype |
---|
12 | import gluon.fileutils |
---|
13 | from gluon._compat import iteritems |
---|
14 | |
---|
15 | is_gae = request.env.web2py_runtime_gae or False |
---|
16 | |
---|
17 | # ## critical --- make a copy of the environment |
---|
18 | |
---|
19 | global_env = copy.copy(globals()) |
---|
20 | global_env['datetime'] = datetime |
---|
21 | |
---|
22 | http_host = request.env.http_host.split(':')[0] |
---|
23 | remote_addr = request.env.remote_addr |
---|
24 | try: |
---|
25 | hosts = (http_host, socket.gethostname(), |
---|
26 | socket.gethostbyname(http_host), |
---|
27 | '::1', '127.0.0.1', '::ffff:127.0.0.1') |
---|
28 | except: |
---|
29 | hosts = (http_host, ) |
---|
30 | |
---|
31 | if request.is_https: |
---|
32 | session.secure() |
---|
33 | elif (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 | |
---|
37 | if 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 |
---|
51 | elif (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)))) |
---|
55 | else: |
---|
56 | response.subtitle = T('Database Administration (appadmin)') |
---|
57 | menu = True |
---|
58 | |
---|
59 | ignore_rw = True |
---|
60 | response.view = 'appadmin.html' |
---|
61 | if 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 | |
---|
72 | if 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 | |
---|
77 | def 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 | |
---|
88 | databases = get_databases(None) |
---|
89 | |
---|
90 | def eval_in_global_env(text): |
---|
91 | exec ('_ret=%s' % text, {}, global_env) |
---|
92 | return global_env['_ret'] |
---|
93 | |
---|
94 | |
---|
95 | def 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 | |
---|
102 | def 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 | |
---|
111 | def get_query(request): |
---|
112 | try: |
---|
113 | return eval_in_global_env(request.vars.query) |
---|
114 | except Exception: |
---|
115 | return None |
---|
116 | |
---|
117 | |
---|
118 | def 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 | # ########################################################### |
---|
135 | def index(): |
---|
136 | return dict(databases=databases) |
---|
137 | |
---|
138 | |
---|
139 | # ########################################################## |
---|
140 | # ## insert a new record |
---|
141 | # ########################################################### |
---|
142 | |
---|
143 | |
---|
144 | def 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 | |
---|
157 | def download(): |
---|
158 | import os |
---|
159 | db = get_database(request) |
---|
160 | return response.download(request, db) |
---|
161 | |
---|
162 | |
---|
163 | def 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 | |
---|
176 | def import_csv(table, file): |
---|
177 | table.import_from_csv_file(file) |
---|
178 | |
---|
179 | |
---|
180 | def 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 | |
---|
308 | def 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 | |
---|
352 | def state(): |
---|
353 | return dict() |
---|
354 | |
---|
355 | |
---|
356 | def 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 | |
---|
516 | def 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 | |
---|
562 | def 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 | |
---|
604 | def 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 | |
---|
652 | def 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) |
---|