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