Package turbogears :: Package toolbox :: Package catwalk

Source Code for Package turbogears.toolbox.catwalk

   1  """CatWalk - model browser for TurboGears""" 
   2   
   3  __version__ = '0.9.4' 
   4  __author__ = 'Ronald Jaramillo' 
   5  __email__ = 'ronald@checkandhsare.com' 
   6  __copyright__ = 'Copyright 2005 Ronald Jaramillo' 
   7  __license__ = 'MIT' 
   8   
   9  import cPickle as pickle 
  10  import datetime 
  11  import decimal 
  12  import os 
  13  import re 
  14  import socket 
  15  import struct 
  16  import time 
  17   
  18  import pkg_resources 
  19   
  20  import cherrypy 
  21   
  22  try: 
  23      import sqlobject 
  24  except ImportError: 
  25      sqlobject = None 
  26   
  27  import turbogears 
  28  from turbogears import config, expose, identity 
  29   
  30  from browse import Browse 
  31   
  32  date_parser = re.compile(r"""^ 
  33      (?P<year>\d{4,4}) 
  34      (?: 
  35          - 
  36          (?P<month>\d{1,2}) 
  37          (?: 
  38              - 
  39              (?P<day>\d{1,2}) 
  40              (?: 
  41                  T 
  42                  (?P<hour>\d{1,2}) 
  43                  : 
  44                  (?P<minute>\d{1,2}) 
  45                  (?: 
  46                      : 
  47                      (?P<second>\d{1,2}) 
  48                      (?: 
  49                          \. 
  50                          (?P<dec_second>\d+)? 
  51                      )? 
  52                  )? 
  53                  (?: 
  54                      Z 
  55                      | 
  56                      (?: 
  57                          (?P<tz_sign>[+-]) 
  58                          (?P<tz_hour>\d{1,2}) 
  59                          : 
  60                          (?P<tz_min>\d{2,2}) 
  61                      ) 
  62                  ) 
  63              )? 
  64          )? 
  65      )? 
  66  $""", re.VERBOSE) 
67 68 69 -def parse_datetime(s):
70 """Parse a string and return a datetime object.""" 71 assert isinstance(s, basestring) 72 r = date_parser.search(s) 73 try: 74 a = r.groupdict('0') 75 except: 76 raise ValueError, 'invalid date string format' 77 dt = datetime.datetime( 78 int(a['year']), int(a['month']) or 1, int(a['day']) or 1, 79 # If not given these will default to 00:00:00.0 80 int(a['hour']), int(a['minute']), int(a['second']), 81 # Convert into microseconds 82 int(a['dec_second'])*100000) 83 t = datetime.timedelta(hours=int(a['tz_hour']), minutes=int(a['tz_min'])) 84 if a.get('tz_sign', '+') == "-": 85 return dt + t 86 else: 87 return dt - t
88
89 90 -class CatWalk(turbogears.controllers.Controller):
91 """Model Browser. 92 93 An administration tool for listing, creating, updating or deleting 94 your SQLObject instances. 95 96 """ 97 98 __label__ = 'CatWalk' 99 __version__ ='"0.9' 100 __author__ = 'Ronald Jaramillo' 101 __email__ = 'ronald@checkandshare.com' 102 __copyright__ = 'Copyright 2005 Ronald Jaramillo' 103 __license__ = 'MIT' 104 browse = Browse() 105 need_project = True 106 icon = "/tg_static/images/catwalk.png" 107
108 - def __init__(self, model=None):
109 """CatWalk's initializer. 110 111 @param model: reference to a project model module 112 @type model: yourproject.model 113 114 """ 115 116 try: 117 if not sqlobject: 118 raise ImportError("The SQLObject package is not installed.") 119 if not model: 120 model = turbogears.util.get_model() 121 if not model: 122 raise ImportError("No SQLObject model found.\n" 123 "If you are mounting CatWalk to your controller,\n" 124 "remember to import your model and pass a reference to it.") 125 self.model = model 126 if not self.models(): 127 raise ImportError("The SQLObject model is empty.") 128 try: 129 self._connection = model.hub 130 except Exception: 131 self._connection = sqlobject.sqlhub 132 except Exception, e: 133 raise ImportError( 134 "CatWalk failed to load your model file.\n" + str(e)) 135 self.browse.catwalk = self 136 self.register_static_directory()
137
138 - def register_static_directory(self):
139 static_directory = pkg_resources.resource_filename(__name__, 'static') 140 turbogears.config.update({'/tg_toolbox/catwalk': { 141 'tools.staticdir.on' : True, 142 'tools.staticdir.dir': static_directory}})
143 144 @expose('json')
145 - def error(self, msg=''):
146 """Generic error handler for json replies.""" 147 return dict(error=msg)
148
149 - def load_object(self, object_name):
150 """Return a class reference from the models module by name. 151 152 @param object_name: name of the object 153 @type object_name: string 154 155 """ 156 try: 157 obj = getattr(self.model, object_name) 158 except: 159 msg = 'Fail to get reference to object %s' % object_name 160 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg)) 161 return obj
162
163 - def load_instance(self, object_name, id):
164 """Return and instance of the named object with the requested id""" 165 obj = self.load_object(object_name) 166 try: 167 return obj.get(id) 168 except: 169 msg ='Fail to get instance of object: %s with id: %s' % ( 170 object_name, id) 171 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg))
172
173 - def object_field(self, row, column):
174 """Get object field. 175 176 Returns a dict containing the column name and value for the 177 specific column and row, 178 179 @param row: model instance 180 @param column: dict containing columnName, title, type, 181 eventually join, joinMethodName and/or options 182 @type column: dict 183 184 """ 185 column_type = column.get('type', '') 186 if column_type == 'SOSingleJoin': 187 try: 188 subject = getattr(row, column['joinMethodName']) 189 value = '%s' % getattr(subject, column['labelColumn']) 190 except Exception: 191 return {'column': column['columnName'], 192 'value': 'None', 'id': '0'} 193 return {'column': column['columnName'], 'value': value} 194 elif column_type in ('SORelatedJoin', 'SOSQLRelatedJoin'): 195 return self.related_join_count(row, column) 196 elif column_type in ('SOMultipleJoin', 'SOSQLMultipleJoin'): 197 return self.multiple_join_count(row, column) 198 elif column_type == 'SOForeignKey': 199 return self.object_field_for_foreign_key(row, column) 200 elif column_type == 'SOStringCol': 201 value = getattr(row, column['columnName']) 202 value = self.encode_label(value) 203 return {'column': column['columnName'], 'value': value} 204 else: 205 value = getattr(row, column['columnName']) 206 if value is not None: 207 try: 208 value = u'%s' % value 209 except UnicodeDecodeError: 210 value = unicode(value, 'UTF-8') 211 return {'column': column['columnName'], 'value': value}
212
213 - def multiple_join_count(self, row, column):
214 """Return the total number of related objects.""" 215 try: 216 columnObject = getattr(self.model, column['join']) 217 for clm in columnObject.sqlmeta.columnList: 218 if type(clm) == sqlobject.SOForeignKey: 219 if column['objectName'] == clm.foreignKey: 220 foreign_key = clm 221 fkName = foreign_key.name 222 fkQuery = getattr(columnObject.q, str(fkName)) 223 select = columnObject.select(fkQuery == row.id) 224 value = '%s' % select.count() 225 except Exception: 226 value = '0' 227 return {'column': column['joinMethodName'], 'value': value}
228
229 - def related_join_count(self, row, column):
230 """Return the total number of related objects.""" 231 try: 232 value = '%s' % len(list(getattr(row, column['joinMethodName']))) 233 except Exception: 234 value = '0' 235 return {'column': column['joinMethodName'], 'value': value}
236
237 - def object_field_for_foreign_key(self, row, column):
238 """Return the foreign key value.""" 239 try: 240 name = getattr(row, column['columnName']) 241 value = getattr(name, column['labelColumn']) 242 value = self.encode_label(value) 243 except AttributeError: 244 return {'column': column['columnName'], 245 'value': 'None', 'id': '0'} 246 return {'column': column['columnName'], 247 'value': value, 'id': '%s' % name.id}
248
249 - def update_object(self, object_name, id, values):
250 self.load_object(object_name) 251 instance = self.load_instance(object_name, id) 252 columns = self.object_columns(object_name) 253 parameters = self.extract_parameters(columns, values) 254 instance.set(**parameters)
255
256 - def remove_object(self, object_name, id):
257 """Remove the object by id.""" 258 obj = self.load_object(object_name) 259 try: 260 obj.delete(id) 261 except: 262 msg = 'Fail to delete instance id: %s for object: %s' % ( 263 id, object_name) 264 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg))
265
266 - def extract_parameters(self, cols, values):
267 """Loop trough the columns and extract the values from the dictionary. 268 269 @param cols: column list 270 @param values: dict of submitted values 271 272 """ 273 params = {} 274 for col in cols: 275 column_name = col['columnName'] 276 if column_name not in values: 277 continue 278 value = values[column_name] 279 not_none = col['notNone'] 280 column_type = col['type'] 281 if column_type == 'SODateTimeCol': 282 try: 283 value = parse_datetime('%sZ' % value.replace(' ', 'T')) 284 except ValueError: 285 value = None 286 elif column_type == 'SOBoolCol': 287 try: 288 value= bool(int(value)) 289 except ValueError: 290 if not_none: 291 value = False 292 else: 293 value = None 294 elif column_type == 'SOFloatCol': 295 try: 296 value = float(value) 297 except ValueError: 298 if not_none: 299 value = 0.0 300 else: 301 value = None 302 elif column_type == 'SOIntCol': 303 try: 304 value = int(value) 305 except ValueError: 306 if not_none: 307 value = 0 308 else: 309 value = None 310 elif column_type in ('SODecimalCol','SOCurrencyCol'): 311 value = self.extract_decimal_value(value, not_none) 312 elif column_type == 'SOForeignKey': 313 value = self.extract_foreign_key(value, not_none) 314 else: 315 if not (value or not_none): 316 value = None 317 params[column_name] = values[column_name] = value 318 return params
319
320 - def extract_foreign_key(self, value, not_none):
321 if value == '__default_none__': 322 return value 323 try: 324 return int(value) 325 except ValueError: 326 if not_none: 327 return 0 328 else: 329 return None
330
331 - def extract_decimal_value(self, value, not_none):
332 try: 333 return decimal.Decimal(value) 334 except (TypeError, ValueError, decimal.InvalidOperation): 335 if not_none: 336 return decimal.Decimal('0.0') 337 else: 338 return None
339
340 - def object_instances(self, object_name, start=0):
341 """Return dictionary containing all instances for the requested object. 342 343 @param object_name: name of the object 344 @type object_name: string 345 346 """ 347 obj = self.load_object(object_name) 348 total = 0 349 page_size = 10 350 start = int(start) 351 try: 352 query = obj.select() 353 total = query.count() 354 results = query[start:start+page_size] 355 headers, rows = self.headers_and_rows(object_name, list(results)) 356 except Exception, e: 357 msg = 'Fail to load object instance: %s' % e 358 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg)) 359 return dict(objectName=object_name, 360 rows=rows, headers=headers, start=start, 361 page_size=page_size, total=total, 362 hidden_columns=self.load_columns_visibility_state(object_name))
363
364 - def foreign_key_alternatives(self, foreign_key, column_label):
365 """Return list of dictionaries containing the posible foreignKey values. 366 367 @param foreign_key: name of the foreignKey object 368 @type foreign_key: string 369 @param column_label: name of the column to use as instance identifier 370 @type column_label: string 371 372 """ 373 obj = self.load_object(foreign_key) 374 alt = [] 375 for x in list(obj.select()): 376 label = self.encode_label(getattr(x, column_label)) 377 alt.append({'id': type(x.id)(x.id), 'label': label}) 378 return alt
379
380 - def encode_label(self, label):
381 try: 382 return unicode(label, 'UTF-8') 383 except TypeError: 384 return u'%s' % label # this is an integer (fx. an id) 385 except UnicodeEncodeError: 386 return u'%s' % label
387
388 - def headers_and_rows(self, objectName, rows):
389 """Return a tuple containing a list of rows and header labels. 390 391 @param objectName: name of the object 392 @type objectName: string 393 @param rows: list of intances 394 395 """ 396 cols = self.object_columns(objectName) 397 labels = [{'column': col['columnName'], 398 'label': (col['title'] or col['columnName'])} for col in cols] 399 labels.insert(0, {'column': 'id', 'label': 'ID'}) 400 values = [] 401 for row in rows: 402 tmp = [] 403 tmp.append(self.object_field(row, {'columnName': 'id'})) 404 for col in cols: 405 col['objectName'] = objectName 406 tmp.append(self.object_field(row, col)) 407 values.append(list(tmp)) 408 return labels, values
409
410 - def object_joins(self, objectName, id, join, joinType, joinObjectName=''):
411 """Collect the joined instances into a dictionary. 412 413 @param objectName: name of the object 414 @type objectName: string 415 @param id: id of the instance 416 @type id: string 417 @param join: name of the join (joinMethodName in SQLObject parlance) 418 @type join: string 419 @param joinType: name of join type 420 @type joinType: string 421 @param joinObjectName: otherClassName (in SQLObject parlance) 422 @type joinObjectName: string 423 424 """ 425 hostObject = objectName 426 obj = self.load_object(objectName) 427 try: 428 rows = list(getattr(obj.get(id), join)) 429 except: 430 msg = 'Error, joins objectName: %s, id: %s, join: %s' % ( 431 objectName, id, join) 432 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg)) 433 434 view = '%s_%s' % (hostObject, joinObjectName) 435 hidden_columns = self.load_columns_visibility_state(view) 436 437 joinsDict = dict(objectName=objectName, rows=[], headers=[], 438 join=join, id=id, joinType=joinType, joinObjectName=joinObjectName, 439 hostObject=hostObject, hidden_columns=hidden_columns) 440 if not rows: 441 return joinsDict 442 443 c = '%s' % rows[0].sqlmeta.soClass 444 objectName = c.split('.')[-1].replace("'>", '') 445 headers, rows = self.headers_and_rows(objectName, rows) 446 joinsDict['objectName'] = objectName 447 joinsDict['rows'] = rows 448 joinsDict['headers'] = headers 449 return joinsDict
450
451 - def object_representation(self, obj):
452 """Utility method that returns a stripped object representation.""" 453 return str(obj).replace('<', '').replace('>', '')
454
455 - def get_column_type(self, column):
456 """Given a column representation return the column type.""" 457 column_type = '%r' % column 458 return column_type.split()[0][1:].split('.')[-1]
459
460 - def column_title(self, column):
461 if isinstance(column, sqlobject.col.SOForeignKey): 462 return column.name.replace('ID', '') 463 try: 464 title = getattr(column,'title') or '' 465 except AttributeError: 466 title = '' 467 return title
468
469 - def column_default(self, column, column_type):
470 try: 471 default = column.default 472 except AttributeError: 473 return '' 474 if default == sqlobject.sqlbuilder.NoDefault: 475 return '' 476 if column_type in ('SOIntCol', 'SOFloatCol', 'SOStringCol', 477 'SODecimalCol', 'SOCurrencyCol', 'SOBoolCol'): 478 return default 479 elif column_type == 'SODateTimeCol': 480 d = '%s' % default 481 return ':'.join(d.split(':')[:-1]) 482 elif column_type == 'SODateCol': 483 d = '%s' % default 484 return d.split(None, 1)[0] 485 return ''
486
487 - def column_not_none(self, column):
488 try: 489 return column.notNone 490 except AttributeError: 491 return False
492
493 - def get_column_properties(self, column_name, column):
494 """Return a dictionary containing the column properties. 495 496 Depending on the column type the properties returned could be: 497 type, title, join (otherClassName), joinMethodName, 498 length, varchar, labelColumn, options 499 500 @param column_name: name of the column 501 @type column_name: string 502 @param column: column instance 503 504 """ 505 props = {'type': self.get_column_type(column), 506 'columnName': column_name} 507 props['title'] = self.column_title(column) 508 props['default'] = self.column_default(column, props['type']) 509 props['notNone'] = self.column_not_none(column) 510 if props['type'] == 'SOEnumCol': 511 props['options'] = column.enumValues 512 if props['type'] in ('SOMultipleJoin', 'SOSQLMultipleJoin', 513 'SORelatedJoin', 'SOSQLRelatedJoin'): 514 props['join'] = column.otherClassName 515 props['joinMethodName'] = column.joinMethodName 516 if props['type'] == 'SOSingleJoin': 517 props['join'] = column.otherClassName 518 props['joinMethodName'] = column.joinMethodName 519 props['labelColumn'] = self.load_label_column_for_object( 520 column.otherClassName) 521 props['options'] = self.foreign_key_alternatives( 522 column.otherClassName, props['labelColumn']) 523 props['objectName'] = column.soClass.__name__ 524 if props['type'] in ['SOStringCol', 'SOUnicodeCol']: 525 props = self.get_string_properties(column, props) 526 if props['type'] == 'SOForeignKey': 527 props = self.get_foreign_key_properties(column, props) 528 return props
529
530 - def get_string_properties(self, column, properties):
531 """Extract the SOStringCol properties from the column object.""" 532 properties['length'] = column.length 533 properties['varchar'] = column.varchar 534 return properties
535
536 - def get_foreign_key_properties(self, column, properties):
537 """Extract the foreignKey properties from the column object.""" 538 properties['join'] = column.foreignKey 539 properties['columnName'] = column.foreignName 540 properties['labelColumn'] = self.load_label_column_for_object( 541 column.foreignKey) 542 properties['options'] = self.foreign_key_alternatives( 543 column.foreignKey, properties['labelColumn']) 544 if not column.notNone: 545 properties['options'].insert(0, 546 {'id': '__default_none__', 'label': 'None'}) 547 return properties
548
549 - def object_columns(self, object_name):
550 """Return list of columns properties arranged in dicts. 551 552 @param object_name: name of the object 553 @type object_name: string 554 555 """ 556 obj = self.load_object(object_name) 557 cols = self.get_columns_for_object(obj) 558 return self.order_columns(object_name, cols)
559
560 - def get_columns_for_object(self, obj):
561 """Return list of columns properties arranged in dicts. 562 563 @param object: model instance 564 565 """ 566 cols = [] 567 # get normal columns 568 for column_name in obj.sqlmeta.columns: 569 column = obj.sqlmeta.columns[column_name] 570 if obj._inheritable and column_name == 'childName': 571 continue 572 cols.append(self.get_column_properties(column_name, column)) 573 # get join columns 574 for column in obj.sqlmeta.joins: 575 cols.append(self.get_column_properties( 576 column.joinMethodName, column)) 577 # get inherited columns 578 if obj._inheritable and not self.is_inheritable_base_class(obj): 579 inherited_columns = self.get_inherited_columns(obj) 580 if inherited_columns: 581 cols.extend(inherited_columns) 582 return cols
583
584 - def get_inherited_columns(self, obj):
585 """Return the columns inherited from the parent class""" 586 return self.get_columns_for_object(obj._parentClass)
587
588 - def is_inheritable_base_class(self, obj):
589 """Check if the object is a direct subclass of InheritableSQLObject""" 590 return 'sqlobject.inheritance.InheritableSQLObject' in str(obj.__bases__)
591 592 ## configuration state ## 593
594 - def state_path(self):
595 """Return the path to the catwalk session pickle. 596 597 By default this is located in a directory named 'catwalk-session' 598 beneath your application's package directory or, if the package name 599 can not be determined, below the current directory. 600 601 The directory must be writable by the server and will be created if it 602 does not exist. You can specify a different directory if you set the 603 config setting 'catwalk.session_dir' to an absolute path. 604 605 """ 606 catwalk_session_dir = os.path.join(config.get('catwalk.session_dir', 607 turbogears.util.get_package_name() or os.curdir), 'catwalk-session') 608 catwalk_session_dir = os.path.abspath(catwalk_session_dir) 609 if not os.path.exists(catwalk_session_dir): 610 try: 611 os.mkdir(catwalk_session_dir) 612 except (IOError, OSError), e: 613 msg = 'Fail to create session directory %s' % e 614 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg)) 615 return os.path.join(catwalk_session_dir, 'session.pkl')
616
617 - def load_state(self):
618 """Retrieve the pickled state from disc.""" 619 if not os.path.exists(self.state_path()): 620 return {} 621 try: 622 return pickle.load(open(self.state_path(),'rb')) 623 except pickle.PicklingError, e: 624 msg = 'Fail to load pickled session file %s' % e 625 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg))
626
627 - def save_state(self, state):
628 """Pickle the state.""" 629 try: 630 pickle.dump(state, open(self.state_path(), 'wb'), True) 631 except pickle.PicklingError, e: 632 msg = 'Fail to store pickled session file %s' % e 633 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg))
634
635 - def hide_columns(self, view, columns=''):
636 state = self.load_state() 637 hidden_columns = state.get('hidden_columns', {}) 638 hidden_columns[view] = columns.split('|') 639 state['hidden_columns'] = hidden_columns 640 self.save_state(state)
641
642 - def toggle_columns_visibility_state(self, view, columns):
643 """Toggle the columns visibility and store the new state. 644 645 @param view: name of the grid view to be stored 646 @type view: string 647 @param columns: name of the columns to be hidden or shown 648 @type column: bar separated string 649 650 """ 651 state = self.load_state() 652 hidden_columns = state.get('hidden_columns', {}) 653 if not columns: 654 hidden_columns[view] = [] 655 hidden_columns_list = [] 656 else: 657 hidden_columns_list = hidden_columns.get(view, []) 658 columns = columns.split('|') 659 for column in columns: 660 661 if column in hidden_columns_list: 662 hidden_columns_list = [x for x in hidden_columns_list 663 if x != column] 664 else: 665 hidden_columns_list.append(column) 666 667 hidden_columns[view] = hidden_columns_list 668 state['hidden_columns'] = hidden_columns 669 self.save_state(state)
670
671 - def load_columns_visibility_state(self, view):
672 """Return a list of hidden columns names for the requested view. 673 674 @param view: name of the grid view to be stored 675 @type view: string 676 677 """ 678 state = self.load_state() 679 hidden_columns = state.get('hidden_columns', {}) 680 return hidden_columns.get(view, [])
681
682 - def load_label_column_for_object(self, objectName):
683 """Get the column name (foreignKey label) for an object. 684 685 686 @param objectName: name of the object 687 @type objectName: string 688 689 """ 690 state = self.load_state() 691 lables = state.get('columns', {}) 692 return lables.get(objectName, 'id')
693
694 - def column_label_for_object(self, objectName, columnName):
695 """Store the column name (foreignKey label) for an object. 696 697 @param objectName: name of the object 698 @type objectName: string 699 @param columnName: name of the column to use as foreignKey label 700 @type columnName: string 701 702 """ 703 state = self.load_state() 704 cl = state.get('columns', {}) 705 cl[objectName] = columnName 706 state['columns'] = cl 707 self.save_state(state)
708
709 - def load_column_order(self, object_name):
710 """Get the column order. 711 712 If the user has rearranged the columns order for an object, 713 this will return the preferred order as list. 714 715 @param object_name: name of the object 716 @type object_name: string 717 718 """ 719 state = self.load_state() 720 cols = state.get('order', {}) 721 return cols.get(object_name, [])
722
723 - def save_column_order(self, object_name, columns_bsv):
724 """Save the preferred order of the object's columns. 725 726 @param object_name: name of the object 727 @type object_name: string 728 @param columns_bsv: bar (|) delimited columns names 729 @type columns_bsv: string 730 731 """ 732 state = self.load_state() 733 cl = state.get('order', {}) 734 cl[object_name] = columns_bsv.split('|') 735 state['order'] = cl 736 self.save_state(state)
737
738 - def order_columns(self, object_name, cols):
739 """Return a rearranged list of columns as configured by the user. 740 741 @param object_name: name of the object 742 @type object_name: string 743 @param cols: original list of columns following the default table order 744 @type cols: list 745 746 """ 747 order = self.load_column_order(object_name) 748 if not order: 749 return cols 750 c = {} 751 for col in cols: 752 c[col['columnName']] = col 753 if col['columnName'] not in order: 754 order.append(col['columnName']) 755 rearrenged = [] 756 for columnName in order: 757 if columnName not in c.keys(): 758 continue 759 rearrenged.append(c[columnName]) 760 return rearrenged
761
762 - def save_model_order(self, models):
763 """Store the new order of the listed models.""" 764 state = self.load_state() 765 state['model_order'] = models.split('|') 766 self.save_state(state)
767
768 - def load_models_order(self):
769 state = self.load_state() 770 return state.get('model_order', [])
771
772 - def order_models(self, models):
773 ordered = self.load_models_order() 774 if not ordered: 775 return models 776 #add any new models to the ordered list 777 for model in models: 778 if not model in ordered: 779 ordered.append(model) 780 reorderedList = [] 781 #check that the ordered list don't have delete models 782 for model in ordered: 783 if model in models: 784 reorderedList.append(model) 785 return reorderedList
786 787 ## exposed methods ## 788 789 @expose('json')
790 - def add(self, **v):
791 """Create a new instance of an object. 792 793 @param v: dictionary of submitted values 794 795 """ 796 objectName = v['objectName'] 797 obj = self.load_object(objectName) 798 cols = self.object_columns(objectName) 799 params = self.extract_parameters(cols, v) 800 if not params: 801 return self.instances(objectName) 802 try: 803 new_object = obj(**params) 804 except Exception, e: 805 cherrypy.response.status = 500 806 return dict(error=str(e)) 807 if not new_object: 808 return self.instances(objectName) 809 returnlist = self.object_instance(objectName,'%s' % new_object.id) 810 returnlist["msg"] = "A new instance of %s was created" % objectName 811 return returnlist
812 813 @expose('json')
814 - def update(self, **values):
815 """Update the objects properties. 816 817 @param values: dictionary of key and values, as a bare minimum 818 the name of the object (objectName) and the id 819 820 """ 821 object_name = values.get('objectName', '') 822 id = values.get('id', '') 823 try: 824 self.update_object(object_name, id, values) 825 except Exception, e: 826 cherrypy.response.status = 500 827 return dict(error=str(e)) 828 returnlist = self.object_instances(object_name) 829 returnlist["msg"] = "The object was successfully updated" 830 return returnlist
831 832 @expose('json')
833 - def remove(self, objectName, id):
834 """Remove and instance by id. 835 836 This doesn't handle references (cascade delete). 837 838 @param objectName: name of the object 839 @type objectName: string 840 @param id: id of the instance to be removed 841 @type id: string 842 843 """ 844 try: 845 self.remove_object(objectName, id) 846 except Exception, e: 847 cherrypy.response.status = 500 848 return dict(error=str(e)) 849 returnlist = self.object_instances(objectName) 850 returnlist["msg"] = "The object was successfully deleted" 851 return returnlist
852 853 @expose('json')
854 - def remove_single_join(self, object_name, id, 855 join_object_name, join_object_id):
856 """Remove a single join instance by id. 857 858 This doesn't handle references (cascade delete). 859 860 @param object_name: name of the host object 861 @type object_name: string 862 @param id: id of the host instance 863 @type id: string 864 @param join_object_name: name of the join object 865 @type join_object_name: string 866 @param join_object_id: id of the join instance to be removed 867 @type join_object_id: string 868 869 """ 870 self.remove_object(join_object_name, join_object_id) 871 return self.object_instance(object_name, id)
872 873 @expose('json')
874 - def saveModelOrder(self, models):
875 """Save the preferred order of the listed models.""" 876 self.save_model_order(models) 877 return '' #return dummy string, else json will barf
878 879 @expose('json')
880 - def columnOrder(self, objectName, cols):
881 """Save the preferred order of the object's columns. 882 883 @param objectName: name of the object 884 @type objectName: string 885 @param cols: columns names separated by '|' 886 @type cols: string 887 888 """ 889 self.save_column_order(objectName, cols) 890 return '' # return dummy string, else json will barf
891 892 @expose('json')
893 - def instances(self, objectName, start=0):
894 """Get object instances. 895 896 Returns a JSON structure containing all instances of the 897 requested object. 898 899 @param objectName: name of the object 900 @type objectName: string 901 902 """ 903 return self.object_instances(objectName, start)
904 905 @expose('json')
906 - def manageRelatedJoins(self, objectName, id, 907 join, relatedObjectName, **vargs):
908 """Get related joins. 909 910 Returns a JSON structure with a list of related joins for 911 the requested object, and a list of all joins. 912 913 @param objectName: name of the object 914 @type objectName: string 915 @param id: id of the instance 916 @type id: string 917 @param join: name of the join (joinMethodName in SQLObject parlance) 918 @type join: string 919 @param relatedObjectName: otherClassName (in SQLObject parlance) 920 @type relatedObjectName: string 921 922 """ 923 joins = self.object_joins(objectName, id, 924 join, 'SORelatedJoin', relatedObjectName) 925 joins['allJoins'] = self.object_instances(relatedObjectName) 926 return joins
927 928 @expose('json')
929 - def updateJoins(self, objectName, id, 930 join, joinType, joinObjectName, joins):
931 """Update joins. 932 933 Drop all related joins first, then loop trough the submitted joins 934 and set the relation. 935 936 @param objectName: name of the object 937 @type objectName: string 938 @param id: id of the instance to be removed 939 @type id: string 940 @param join: name of the join field (joinMethodName) 941 @type join: string 942 @param joinType: type of the join (Multiple or Related) 943 @type joinType: string 944 @param joinObjectName: name of the joined object (otherClassName) 945 @type joinObjectName: string 946 @param joins: comma delimited string of join instances id's 947 @type joins: string 948 949 """ 950 try: 951 obj = self.load_object(objectName) 952 inst = obj.get(id) 953 954 # get the add/remove method names 955 j = [j for j in obj.sqlmeta.joins if (j.joinMethodName == join)][0] 956 remove_method = getattr(inst, 'remove' + j.addRemoveName) 957 add_method = getattr(inst, 'add' + j.addRemoveName) 958 959 # remove all joined instances 960 for joined_instance in list(getattr(inst, join)): 961 remove_method(joined_instance) 962 963 # add the new joined instances 964 join_object = self.load_object(joinObjectName) 965 joins = joins.split(',') 966 for i in joins: 967 try: 968 i = int(i) 969 except ValueError: 970 continue 971 instance = join_object.get(i) 972 add_method(instance) 973 except Exception, e: 974 cherrypy.response.status = 500 975 return dict(error=str(e)) 976 returnlist = self.object_instance(objectName, id) 977 returnlist["msg"] = "The object was successfully updated" 978 return returnlist
979 980 @expose('json')
981 - def updateColumns(self, objectName, column):
982 """Update a column. 983 984 Toggle (and store) the state of the requested column 985 in grid view display. 986 987 @param objectName: name of the object 988 @type objectName: string 989 @param column: name of the column to be hidden 990 @type column: string 991 992 993 """ 994 self.toggle_columns_visibility_state(objectName, column) 995 return self.object_instances(objectName)
996 997 @expose('json')
998 - def updateColumnsJoinView(self, objectName, id, 999 join, joinType, joinObjectName, column):
1000 """Update column in join view. 1001 1002 Toggle (and store) the state of the requested column 1003 in grid view display for a join view. 1004 1005 @param objectName: name of the object 1006 @type objectName: string 1007 @param id: id of the 'parent' instance 1008 @type id: string 1009 @param join: name of the join (joinMethodName in SQLObject parlance) 1010 @type join: string 1011 @param joinType: name of join type 1012 @type joinType: string 1013 @param joinObjectName: otherClassName (in SQLObject parlance) 1014 @type joinObjectName: string 1015 @param column: name of the column to be hidden or shown 1016 @type column: string 1017 1018 """ 1019 self.toggle_columns_visibility_state('%s_%s' % ( 1020 objectName, joinObjectName), column) 1021 return self.object_joins(objectName, id, 1022 join, joinType, joinObjectName)
1023 1024 @expose('json')
1025 - def joins(self, objectName, id, join, joinType, joinObjectName):
1026 """Get joins. 1027 1028 Return a JSON structure containing a list joins for 1029 the requested object's joinMethodName. 1030 1031 @param objectName: name of the object 1032 @type objectName: string 1033 @param id: id of the instance 1034 @type id: string 1035 @param join: name of the join (joinMethodName in SQLObject parlance) 1036 @type join: string 1037 @param joinType: name of join type 1038 @type joinType: string 1039 @param joinObjectName: otherClassName (in SQLObject parlance) 1040 @type joinObjectName: string 1041 1042 """ 1043 return self.object_joins(objectName, id, join, joinType, joinObjectName)
1044
1045 - def object_instance(self, object_name, id):
1046 obj = self.load_object(object_name) 1047 inst = obj.get(id) 1048 cols = self.object_columns(object_name) 1049 values = [] 1050 for col in cols: 1051 colForID = col.copy() 1052 colForID['type'] = '' 1053 col['object_id'] = id 1054 if col['type'] in ('SOMultipleJoin', 'SOSQLMultipleJoin', 1055 'SORelatedJoin', 'SOSQLRelatedJoin'): 1056 col['id'] = id 1057 try: 1058 value = '%s' % len(list(getattr(inst, col['columnName']))) 1059 except Exception: 1060 value = '0' 1061 col['value'] = {'value': value, 'column': col['columnName']} 1062 elif col['type'] in ('SOForeignKey', 'SOSingleJoin'): 1063 try: 1064 otherClass = getattr(inst, col['columnName']) 1065 col['id'] = self.object_field(inst, colForID) 1066 col['id']['value'] = '%s' % otherClass.id 1067 try: 1068 label_value = '%s' % getattr( 1069 otherClass, col['labelColumn']) 1070 except AttributeError: 1071 label_value = self.object_representation(otherClass) 1072 label_value = self.encode_label(label_value) 1073 col['value'] = {'column': col['columnName'], 1074 'value': label_value} 1075 except AttributeError: 1076 col['id'] = '__default_none__' 1077 col['value'] = {'column': col['columnName'], 1078 'value': 'None', 'id':'__default_none__'} 1079 else: 1080 col['id'] = self.object_field(inst, colForID) 1081 col['value'] = self.object_field(inst, col) 1082 col['objectName'] = object_name 1083 values.append(col) 1084 return dict(objectName=object_name, id=id, values=values)
1085 1086 @expose('json')
1087 - def instance(self, objectName, id):
1088 """Get object instance. 1089 1090 Return a JSON structure containing the columns and field values for 1091 the requested object 1092 1093 @param objectName: name of the object 1094 @type objectName: string 1095 @param id: id of the instance 1096 @type id: string 1097 1098 """ 1099 return self.object_instance(objectName, id)
1100 1101 @expose('json')
1102 - def columnsForLabel(self, objectName, foreignObjectName, foreignKeyName):
1103 """Get columns for label. 1104 1105 Return a JSON structure with a list of columns to use 1106 as foreignKey label. 1107 1108 @param objectName: name of the object 1109 @type objectName: string 1110 @param foreignObjectName: name of the object the foreignKey refers to 1111 @type foreignObjectName: string 1112 @param foreignKeyName: name of the object foreignKey field 1113 @type foreignKeyName: string 1114 1115 """ 1116 cols = [{'columnName': col['columnName'], 'title': col['title']} 1117 for col in self.object_columns(foreignObjectName) 1118 if col['type'] !='SOMultipleJoin'] 1119 return dict(columns=cols, foreignKeyName=foreignKeyName, 1120 foreignKeyColumnForLabel=self.load_label_column_for_object( 1121 foreignObjectName), foreignObjectName=foreignObjectName, 1122 objectName=objectName)
1123 1124 @expose('json')
1125 - def setColumnForLabel(self, objectName, foreignObjectName, 1126 foreignKeyName, columnName):
1127 """Set columns for label. 1128 1129 Exposed method that let you store the column name to be used as 1130 foreignKey label for the requested object. 1131 1132 @param objectName: name of the object 1133 @type objectName: string 1134 @param foreignObjectName: name of the object the foreignKey refers to 1135 @type foreignObjectName: string 1136 @param foreignKeyName: name of the object foreignKey field 1137 @type foreignKeyName: string 1138 @param columnName: name of the column to use as foreignKey label 1139 @type columnName: string 1140 1141 """ 1142 self.column_label_for_object(foreignObjectName, columnName) 1143 return self.columns(objectName)
1144 1145 @expose('json')
1146 - def columns(self, objectName, **kv):
1147 """Return JSON structure containing a list of column properties. 1148 1149 @param objectName: name of the object 1150 @type objectName: string 1151 1152 """ 1153 reorder = 'foreignObjectName' not in kv 1154 return dict(objectName=objectName, 1155 columns=self.object_columns(objectName), 1156 reorder=reorder, reordering=kv)
1157
1158 - def models(self):
1159 objs = [] 1160 for m in dir(self.model): 1161 if m in ('SQLObject', 'InheritableSQLObject'): 1162 continue 1163 c = getattr(self.model, m) 1164 if isinstance(c, type) and issubclass(c, sqlobject.SQLObject): 1165 objs.append(m) 1166 return self.order_models(objs)
1167 1168 @expose('json')
1169 - def list(self):
1170 """Return JSON structure containing a list of available objects.""" 1171 return dict(SQLObjects=self.models())
1172 1173 @expose('turbogears.toolbox.catwalk.catwalk')
1174 - def index(self):
1175 """Main CatWalk page. 1176 1177 Import the proper client side libraries and set up the placeholder 1178 for the dynamic elements. 1179 1180 """ 1181 return dict(models=self.models())
1182