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 turbogears.config.update({'log_debug_info_filter.on': False})
137 self.register_static_directory()
138
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
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
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
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
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
237
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
256
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
320
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
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
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
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
382 try:
383 return unicode(label, 'UTF-8')
384 except TypeError:
385 return u'%s' % label
386 except UnicodeEncodeError:
387 return u'%s' % label
388
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
453 """Utility method that returns a stripped object representation."""
454 return str(obj).replace('<', '').replace('>', '')
455
457 """Given a column representation return the column type."""
458 column_type = '%r' % column
459 return column_type.split()[0][1:].split('.')[-1]
460
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
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
489 try:
490 return column.notNone
491 except AttributeError:
492 return False
493
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
532 """Extract the SOStringCol properties from the column object."""
533 properties['length'] = column.length
534 properties['varchar'] = column.varchar
535 return properties
536
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
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
584
586 """Return the columns inherited from the parent class"""
587 return self.get_columns_for_object(obj._parentClass)
588
590 """Check if the object is a direct subclass of InheritableSQLObject"""
591 return 'sqlobject.inheritance.InheritableSQLObject' in str(obj.__bases__)
592
593
594
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
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
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
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
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
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
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
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
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
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
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
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
770 state = self.load_state()
771 return state.get('model_order', [])
772
787
788
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")
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")
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")
879
880 @expose(format="json")
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 ''
892
893 @expose(format="json")
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")
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
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
961 for joined_instance in list(getattr(inst, join)):
962 remove_method(joined_instance)
963
964
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")
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")
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
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")
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
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")
1171 """Return JSON structure containing a list of available objects."""
1172 return dict(SQLObjects=self.models())
1173
1174 @expose(template='turbogears.toolbox.catwalk.catwalk')
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