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)
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
80 int(a['hour']), int(a['minute']), int(a['second']),
81
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
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
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
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
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
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
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
236
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
255
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
319
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
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
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
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
381 try:
382 return unicode(label, 'UTF-8')
383 except TypeError:
384 return u'%s' % label
385 except UnicodeEncodeError:
386 return u'%s' % label
387
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
452 """Utility method that returns a stripped object representation."""
453 return str(obj).replace('<', '').replace('>', '')
454
456 """Given a column representation return the column type."""
457 column_type = '%r' % column
458 return column_type.split()[0][1:].split('.')[-1]
459
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
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
488 try:
489 return column.notNone
490 except AttributeError:
491 return False
492
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
531 """Extract the SOStringCol properties from the column object."""
532 properties['length'] = column.length
533 properties['varchar'] = column.varchar
534 return properties
535
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
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
583
585 """Return the columns inherited from the parent class"""
586 return self.get_columns_for_object(obj._parentClass)
587
589 """Check if the object is a direct subclass of InheritableSQLObject"""
590 return 'sqlobject.inheritance.InheritableSQLObject' in str(obj.__bases__)
591
592
593
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
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
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
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
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
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
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
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
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
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
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
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
769 state = self.load_state()
770 return state.get('model_order', [])
771
786
787
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')
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')
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')
878
879 @expose('json')
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 ''
891
892 @expose('json')
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')
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
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
960 for joined_instance in list(getattr(inst, join)):
961 remove_method(joined_instance)
962
963
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')
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')
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
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')
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
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')
1170 """Return JSON structure containing a list of available objects."""
1171 return dict(SQLObjects=self.models())
1172
1173 @expose('turbogears.toolbox.catwalk.catwalk')
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