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 from browse import Browse
10 import cPickle as pickle
11 import datetime
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 expose, identity
29
30
31 date_parser = re.compile(r"""^
32 (?P<year>\d{4,4})
33 (?:
34 -
35 (?P<month>\d{1,2})
36 (?:
37 -
38 (?P<day>\d{1,2})
39 (?:
40 T
41 (?P<hour>\d{1,2})
42 :
43 (?P<minute>\d{1,2})
44 (?:
45 :
46 (?P<second>\d{1,2})
47 (?:
48 \.
49 (?P<dec_second>\d+)?
50 )?
51 )?
52 (?:
53 Z
54 |
55 (?:
56 (?P<tz_sign>[+-])
57 (?P<tz_hour>\d{1,2})
58 :
59 (?P<tz_min>\d{2,2})
60 )
61 )
62 )?
63 )?
64 )?
65 $""", re.VERBOSE)
66
67
69 """Parse a string and return a datetime object."""
70 assert isinstance(s, basestring)
71 r = date_parser.search(s)
72 try:
73 a = r.groupdict('0')
74 except:
75 raise ValueError, 'invalid date string format'
76 dt = datetime.datetime(
77 int(a['year']), int(a['month']) or 1, int(a['day']) or 1,
78
79 int(a['hour']), int(a['minute']), int(a['second']),
80
81 int(a['dec_second'])*100000)
82 t = datetime.timedelta(hours=int(a['tz_hour']), minutes=int(a['tz_min']))
83 if a.get('tz_sign', '+') == "-":
84 return dt + t
85 else:
86 return dt - t
87
88
89 -class CatWalk(turbogears.controllers.Controller):
90 """Model Browser.
91
92 An administration tool for listing, creating, updating or deleting
93 your SQLObject instances.
94
95 """
96
97 __label__ = "CatWalk"
98 __version__ = "0.9"
99 __author__ = "Ronald Jaramillo"
100 __email__ = "ronald@checkandshare.com"
101 __copyright__ = "Copyright 2005 Ronald Jaramillo"
102 __license__ = "MIT"
103 browse = Browse()
104 need_project = True
105 icon = "/tg_static/images/catwalk.png"
106
108 """CatWalk's initializer.
109
110 @param model: reference to a project model module
111 @type model: yourproject.model
112
113 """
114
115 try:
116 if not sqlobject:
117 raise ImportError, "The SQLObject package is not installed."
118 if not model:
119 model = turbogears.util.get_model()
120 if not model:
121 raise ImportError, ("No SQLObject model found.\n"
122 "If you are mounting CatWalk to your controller,\n"
123 "remember to import your model and pass a reference to it.")
124 self.model = model
125 if not self.models():
126 raise ImportError, "The SQLObject model is empty."
127 try:
128 self._connection = model.hub
129 except Exception:
130 self._connection = sqlobject.sqlhub
131 except Exception, e:
132 raise ImportError, (
133 "CatWalk failed to load your model file.\n" + str(e))
134 self.browse.catwalk = self
135 turbogears.config.update({'log_debug_info_filter.on': False})
136 self.register_static_directory()
137
139 static_directory = pkg_resources.resource_filename(__name__, 'static')
140 turbogears.config.update({'/tg_toolbox/catwalk': {
141 'static_filter.on': True,
142 'static_filter.dir': static_directory}})
143
144 [expose(format="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 if column.get('type', '') == 'SOSingleJoin':
186 try:
187 subject = getattr(row, column['joinMethodName'])
188 value = '%s' % getattr(subject, column['labelColumn'])
189 except Exception:
190 return {'column': column['columnName'],
191 'value': 'None', 'id': '0'}
192 return {'column': column['columnName'], 'value': value}
193 elif column.get('type','') in ('SORelatedJoin', 'SOSQLRelatedJoin'):
194 return self.related_join_count(row, column)
195 elif column.get('type', '') in ('SOMultipleJoin', 'SOSQLMultipleJoin'):
196 return self.multiple_join_count(row, column)
197 elif column.get('type','') == 'SOForeignKey':
198 return self.object_field_for_foreign_key(row, column)
199 elif column.get('type', '') == 'SOStringCol':
200 value = getattr(row, column['columnName'])
201 value = self.encode_label(value)
202 return {'column': column['columnName'], 'value': value}
203 else:
204 try:
205 value = u'%s' % getattr(row, column['columnName'])
206 except UnicodeDecodeError:
207 value = unicode(getattr(row, column['columnName']), 'UTF-8')
208 return {'column': column['columnName'], 'value': value}
209
211 """Return the total number of related objects."""
212 try:
213 columnObject = getattr(self.model, column['join'])
214 for clm in columnObject.sqlmeta.columnList:
215 if type(clm) == sqlobject.SOForeignKey:
216 if column['objectName'] == clm.foreignKey:
217 foreign_key = clm
218 fkName = foreign_key.name
219 fkQuery = getattr(columnObject.q, str(fkName))
220 select = columnObject.select(fkQuery == row.id)
221 value = '%s' % select.count()
222 except Exception:
223 value = '0'
224 return {'column': column['joinMethodName'], 'value': value}
225
233
235 """Return the foreign key value."""
236 try:
237 name = getattr(row, column['columnName'])
238 value = getattr(name, column['labelColumn'])
239 value = self.encode_label(value)
240 except AttributeError:
241 return {'column': column['columnName'],
242 'value': 'None', 'id': '0'}
243 return {'column': column['columnName'],
244 'value': value, 'id': '%s' % name.id}
245
252
263
265 """Remove the object by id."""
266 obj = self.load_object(object_name)
267 self.remove_related_joins_if_any(object_name, id)
268 try:
269 obj.delete(id)
270 except:
271 msg = 'Fail to delete instance id: %s for object: %s' % (
272 id, object_name)
273 raise cherrypy.HTTPRedirect(turbogears.url('error', msg=msg))
274
276 """Loop trough the columns and extract the values from the dictionary.
277
278 @param cols: column list
279 @param values: dict of submited values
280
281 """
282 params = {}
283 for col in cols:
284 column_name = col['columnName']
285 if values.has_key(column_name):
286 if col['type'] == 'SODateTimeCol':
287 dt = values[column_name]
288 try:
289 b = parse_datetime('%sZ' % dt.replace(' ', 'T'))
290 except ValueError:
291 b = None
292 values[column_name] = b
293 elif col['type'] == 'SOBoolCol':
294 try:
295 b = bool(int(values[column_name]))
296 except ValueError:
297 b = False
298 values[column_name] = b
299 elif col['type'] == 'SOFloatCol':
300 try:
301 b = float(values[column_name])
302 except ValueError:
303 b = 0.0
304 values[column_name] = b
305 elif col['type'] == 'SOIntCol':
306 try:
307 b = int(values[column_name])
308 except ValueError:
309 b = 0
310 values[column_name] = b
311 elif col['type'] == 'SOForeignKey':
312 self.extract_foreign_key(values, column_name)
313 elif col['type'] in ('SODecimalCol','SOCurrencyCol'):
314 self.extract_decimal_value(values, column_name)
3