32 | | This is the supporting material in {{{models/__db.py}}}: |
33 | | {{{ |
34 | | from gluon.storage import Storage |
35 | | # Keep all S3 framework-level elements stored off here, so as to avoid polluting global namespace & to make it clear which part of the framework is being interacted with |
36 | | s3=Storage() |
37 | | s3.crud_fields=Storage() |
38 | | s3.crud_strings=Storage() |
39 | | |
40 | | def shn_crud_strings_lookup(resource): |
41 | | "Look up CRUD strings for a given resource based on the definitions in models/module.py." |
42 | | return getattr(s3.crud_strings,'%s' % resource) |
43 | | |
44 | | def import_csv(table,file): |
45 | | "Import CSV file into Database. Comes from appadmin.py" |
46 | | import csv |
47 | | reader = csv.reader(file) |
48 | | colnames=None |
49 | | for line in reader: |
50 | | if not colnames: |
51 | | colnames=[x[x.find('.')+1:] for x in line] |
52 | | c=[i for i in range(len(line)) if colnames[i]!='id'] |
53 | | else: |
54 | | items=[(colnames[i],line[i]) for i in c] |
55 | | table.insert(**dict(items)) |
56 | | |
57 | | def import_json(table,file): |
58 | | "Import JSON into Database." |
59 | | import gluon.contrib.simplejson as sj |
60 | | reader=sj.loads(file) |
61 | | # ToDo |
62 | | # Get column names |
63 | | # Insert records |
64 | | #table.insert(**dict(items)) |
65 | | return |
66 | | |
67 | | def shn_rest_controller(module,resource,deletable=True,listadd=True,extra=None): |
68 | | """ |
69 | | RESTlike controller function. |
70 | | |
71 | | Provides CRUD operations for the given module/resource. |
72 | | Optional parameters: |
73 | | deletable=False: don't provide visible options for deletion |
74 | | listadd=False: don't provide an add form in the list view |
75 | | extra='field': extra field to display in the list view |
76 | | |
77 | | Anonymous users can Read. |
78 | | Authentication required for Create/Update/Delete. |
79 | | |
80 | | Auditing options for Read &/or Write. |
81 | | |
82 | | Supported Representations: |
83 | | HTML is the default (including full Layout) |
84 | | PLAIN is HTML with no layout |
85 | | - can be inserted into DIVs via AJAX calls |
86 | | - can be useful for clients on low-bandwidth or small screen sizes |
87 | | JSON |
88 | | - read-only for now |
89 | | CSV (useful for synchronization) |
90 | | - List/Display/Create for now |
91 | | AJAX (designed to be run asynchronously to refresh page elements) |
92 | | |
93 | | ToDo: |
94 | | Alternate Representations |
95 | | JSON create/update |
96 | | CSV update |
97 | | SMS,XML,PDF,LDIF |
98 | | Customisable Security Policy |
99 | | """ |
100 | | |
101 | | _table='%s_%s' % (module,resource) |
102 | | table=db[_table] |
103 | | if resource=='setting': |
104 | | s3.crud_strings=shn_crud_strings_lookup(resource) |
105 | | else: |
106 | | s3.crud_strings=shn_crud_strings_lookup(table) |
107 | | |
108 | | # Which representation should output be in? |
109 | | if request.vars.format: |
110 | | representation=str.lower(request.vars.format) |
111 | | else: |
112 | | # Default to HTML |
113 | | representation="html" |
114 | | |
115 | | # Is user logged-in? |
116 | | logged_in = auth.is_logged_in() |
117 | | |
118 | | if len(request.args)==0: |
119 | | # No arguments => default to List (or list_create if logged_in) |
120 | | if session.s3.audit_read: |
121 | | db.s3_audit.insert( |
122 | | person=auth.user.id, |
123 | | operation='list', |
124 | | module=request.controller, |
125 | | resource=resource, |
126 | | old_value='', |
127 | | new_value='' |
128 | | ) |
129 | | if representation=="html": |
130 | | shn_represent(table,resource,deletable,extra) |
131 | | list=t2.itemize(table) |
132 | | if not list: |
133 | | list=s3.crud_strings.msg_list_empty |
134 | | title=s3.crud_strings.title_list |
135 | | subtitle=s3.crud_strings.subtitle_list |
136 | | if logged_in and listadd: |
137 | | # Display the Add form below List |
138 | | if deletable: |
139 | | # Add extra column header to explain the checkboxes |
140 | | if isinstance(list,TABLE): |
141 | | list.insert(0,TR('',B('Delete?'))) |
142 | | form=t2.create(table) |
143 | | # Check for presence of Custom View |
144 | | custom_view='%s_list_create.html' % resource |
145 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
146 | | if os.path.exists(_custom_view): |
147 | | response.view=module+'/'+custom_view |
148 | | else: |
149 | | response.view='list_create.html' |
150 | | addtitle=s3.crud_strings.subtitle_create |
151 | | return dict(module_name=module_name,modules=modules,options=options,list=list,form=form,title=title,subtitle=subtitle,addtitle=addtitle) |
152 | | else: |
153 | | # List only |
154 | | if listadd: |
155 | | add_btn=A(s3.crud_strings.label_create_button,_href=t2.action(resource,'create'),_id='add-btn') |
156 | | else: |
157 | | add_btn='' |
158 | | # Check for presence of Custom View |
159 | | custom_view='%s_list.html' % resource |
160 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
161 | | if os.path.exists(_custom_view): |
162 | | response.view=module+'/'+custom_view |
163 | | else: |
164 | | response.view='list.html' |
165 | | return dict(module_name=module_name,modules=modules,options=options,list=list,title=title,subtitle=subtitle,add_btn=add_btn) |
166 | | elif representation=="ajax": |
167 | | shn_represent(table,resource,deletable,extra) |
168 | | list=t2.itemize(table) |
169 | | if not list: |
170 | | list=s3.crud_strings.msg_list_empty |
171 | | if deletable: |
172 | | # Add extra column header to explain the checkboxes |
173 | | if isinstance(list,TABLE): |
174 | | list.insert(0,TR('',B('Delete?'))) |
175 | | response.view='plain.html' |
176 | | return dict(item=list) |
177 | | elif representation=="plain": |
178 | | list=t2.itemize(table) |
179 | | response.view='plain.html' |
180 | | return dict(item=list) |
181 | | elif representation=="json": |
182 | | list=db().select(table.ALL).json() |
183 | | response.view='plain.html' |
184 | | return dict(item=list) |
185 | | elif representation=="csv": |
186 | | import gluon.contenttype |
187 | | response.headers['Content-Type']=gluon.contenttype.contenttype('.csv') |
188 | | query=db[table].id>0 |
189 | | response.headers['Content-disposition']="attachment; filename=%s_%s_list.csv" % (request.env.server_name,resource) |
190 | | return str(db(query).select()) |
191 | | else: |
192 | | session.error=T("Unsupported format!") |
193 | | redirect(URL(r=request)) |
194 | | else: |
195 | | method=str.lower(request.args[0]) |
196 | | if request.args[0].isdigit(): |
197 | | # 1st argument is ID not method => Display. |
198 | | if session.s3.audit_read: |
199 | | db.s3_audit.insert( |
200 | | person=auth.user.id, |
201 | | operation='read', |
202 | | representation=representation, |
203 | | module=request.controller, |
204 | | resource=resource, |
205 | | record=t2.id, |
206 | | old_value='', |
207 | | new_value='' |
208 | | ) |
209 | | if representation=="html": |
210 | | try: |
211 | | db[table].displays=s3.crud_fields[table] |
212 | | except: |
213 | | pass |
214 | | item=t2.display(table) |
215 | | # Check for presence of Custom View |
216 | | custom_view='%s_display.html' % resource |
217 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
218 | | if os.path.exists(_custom_view): |
219 | | response.view=module+'/'+custom_view |
220 | | else: |
221 | | response.view='display.html' |
222 | | title=s3.crud_strings.title_display |
223 | | edit=A(T("Edit"),_href=t2.action(resource,['update',t2.id]),_id='edit-btn') |
224 | | if deletable: |
225 | | delete=A(T("Delete"),_href=t2.action(resource,['delete',t2.id]),_id='delete-btn') |
226 | | else: |
227 | | delete='' |
228 | | list_btn=A(s3.crud_strings.label_list_button,_href=t2.action(resource),_id='list-btn') |
229 | | return dict(module_name=module_name,modules=modules,options=options,item=item,title=title,edit=edit,delete=delete,list_btn=list_btn) |
230 | | elif representation=="plain": |
231 | | item=t2.display(table) |
232 | | response.view='plain.html' |
233 | | return dict(item=item) |
234 | | elif representation=="json": |
235 | | item=db(table.id==t2.id).select(table.ALL).json() |
236 | | response.view='plain.html' |
237 | | return dict(item=item) |
238 | | elif representation=="csv": |
239 | | import gluon.contenttype |
240 | | response.headers['Content-Type']=gluon.contenttype.contenttype('.csv') |
241 | | query=db[table].id==t2.id |
242 | | response.headers['Content-disposition']="attachment; filename=%s_%s_%d.csv" % (request.env.server_name,resource,t2.id) |
243 | | return str(db(query).select()) |
244 | | elif representation=="rss": |
245 | | #if request.args and request.args[0] in settings.rss_procedures: |
246 | | # feed=eval('%s(*request.args[1:],**dict(request.vars))'%request.args[0]) |
247 | | #else: |
248 | | # t2._error() |
249 | | #import gluon.contrib.rss2 as rss2 |
250 | | #rss = rss2.RSS2( |
251 | | # title=feed['title'], |
252 | | # link = feed['link'], |
253 | | # description = feed['description'], |
254 | | # lastBuildDate = feed['created_on'], |
255 | | # items = [ |
256 | | # rss2.RSSItem( |
257 | | # title = entry['title'], |
258 | | # link = entry['link'], |
259 | | # description = entry['description'], |
260 | | # pubDate = entry['created_on']) for entry in feed['entries']] |
261 | | # ) |
262 | | #response.headers['Content-Type']='application/rss+xml' |
263 | | #return rss2.dumps(rss) |
264 | | response.view='plain.html' |
265 | | return |
266 | | else: |
267 | | session.error=T("Unsupported format!") |
268 | | redirect(URL(r=request)) |
269 | | else: |
270 | | if method=="create": |
271 | | if logged_in: |
272 | | if session.s3.audit_write: |
273 | | audit_id=db.s3_audit.insert( |
274 | | person=auth.user.id, |
275 | | operation='create', |
276 | | representation=representation, |
277 | | module=request.controller, |
278 | | resource=resource, |
279 | | record=t2.id, |
280 | | old_value='', |
281 | | new_value='' |
282 | | ) |
283 | | if representation=="html": |
284 | | t2.messages.record_created=s3.crud_strings.msg_record_created |
285 | | form=t2.create(table) |
286 | | # Check for presence of Custom View |
287 | | custom_view='%s_create.html' % resource |
288 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
289 | | if os.path.exists(_custom_view): |
290 | | response.view=module+'/'+custom_view |
291 | | else: |
292 | | response.view='create.html' |
293 | | title=s3.crud_strings.title_create |
294 | | list_btn=A(s3.crud_strings.label_list_button,_href=t2.action(resource),_id='list-btn') |
295 | | return dict(module_name=module_name,modules=modules,options=options,form=form,title=title,list_btn=list_btn) |
296 | | elif representation=="plain": |
297 | | form=t2.create(table) |
298 | | response.view='plain.html' |
299 | | return dict(item=form) |
300 | | elif representation=="json": |
301 | | # ToDo |
302 | | # Read in POST |
303 | | #file=request.body.read() |
304 | | #import_json(table,file) |
305 | | item='{"Status":"failed","Error":{"StatusCode":501,"Message":"JSON creates not yet supported!"}}' |
306 | | response.view='plain.html' |
307 | | return dict(item=item) |
308 | | elif representation=="csv": |
309 | | # Read in POST |
310 | | file=request.vars.filename.file |
311 | | try: |
312 | | import_csv(table,file) |
313 | | reply=T('Data uploaded') |
314 | | except: |
315 | | reply=T('Unable to parse CSV file!') |
316 | | return reply |
317 | | else: |
318 | | session.error=T("Unsupported format!") |
319 | | redirect(URL(r=request)) |
320 | | else: |
321 | | redirect(URL(r=request,c='default',f='user',args='login',vars={'_next':URL(r=request,c=module,f=resource,args='create')})) |
322 | | elif method=="display": |
323 | | redirect(URL(r=request,args=t2.id)) |
324 | | elif method=="update": |
325 | | if logged_in: |
326 | | if session.s3.audit_write: |
327 | | old_value = [] |
328 | | _old_value=db(db[table].id==t2.id).select()[0] |
329 | | for field in _old_value: |
330 | | old_value.append(field+':'+str(_old_value[field])) |
331 | | audit_id=db.s3_audit.insert( |
332 | | person=t2.person_id, |
333 | | operation='update', |
334 | | representation=representation, |
335 | | module=request.controller, |
336 | | resource=resource, |
337 | | record=t2.id, |
338 | | old_value=old_value, |
339 | | new_value='' |
340 | | ) |
341 | | if representation=="html": |
342 | | t2.messages.record_modified=s3.crud_strings.msg_record_modified |
343 | | form=t2.update(table,deletable=False) |
344 | | # Check for presence of Custom View |
345 | | custom_view='%s_update.html' % resource |
346 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
347 | | if os.path.exists(_custom_view): |
348 | | response.view=module+'/'+custom_view |
349 | | else: |
350 | | response.view='update.html' |
351 | | title=s3.crud_strings.title_update |
352 | | list_btn=A(s3.crud_strings.label_list_button,_href=t2.action(resource),_id='list-btn') |
353 | | return dict(module_name=module_name,modules=modules,options=options,form=form,title=title,list_btn=list_btn) |
354 | | elif representation=="plain": |
355 | | form=t2.update(table,deletable=False) |
356 | | response.view='plain.html' |
357 | | return dict(item=form) |
358 | | elif representation=="json": |
359 | | # ToDo |
360 | | item='{"Status":"failed","Error":{"StatusCode":501,"Message":"JSON updates not yet supported!"}}' |
361 | | response.view='plain.html' |
362 | | return dict(item=item) |
363 | | else: |
364 | | session.error=T("Unsupported format!") |
365 | | redirect(URL(r=request)) |
366 | | else: |
367 | | redirect(URL(r=request,c='default',f='user',args='login',vars={'_next':URL(r=request,c=module,f=resource,args=['update',t2.id])})) |
368 | | elif method=="delete": |
369 | | if logged_in: |
370 | | if session.s3.audit_write: |
371 | | old_value = [] |
372 | | _old_value=db(db[table].id==t2.id).select()[0] |
373 | | for field in _old_value: |
374 | | old_value.append(field+':'+str(_old_value[field])) |
375 | | db.s3_audit.insert( |
376 | | person=auth.user.id, |
377 | | operation='delete', |
378 | | representation=representation, |
379 | | module=request.controller, |
380 | | resource=resource, |
381 | | record=t2.id, |
382 | | old_value=old_value, |
383 | | new_value='' |
384 | | ) |
385 | | t2.messages.record_deleted=s3.crud_strings.msg_record_deleted |
386 | | if representation=="ajax": |
387 | | t2.delete(table,next='%s?format=ajax' % resource) |
388 | | else: |
389 | | t2.delete(table,next=resource) |
390 | | else: |
391 | | redirect(URL(r=request,c='default',f='user',args='login',vars={'_next':URL(r=request,c=module,f=resource,args=['delete',t2.id])})) |
392 | | elif method=="search": |
393 | | if session.s3.audit_read: |
394 | | db.s3_audit.insert( |
395 | | person=auth.user.id, |
396 | | operation='search', |
397 | | module=request.controller, |
398 | | resource=resource, |
399 | | old_value='', |
400 | | new_value='' |
401 | | ) |
402 | | if representation=="html": |
403 | | if logged_in and deletable: |
404 | | db[table].represent=lambda table:shn_list_item(table,resource='%s' % resource,action='display',extra="INPUT(_type='checkbox',_class='delete_row',_name='%s' % resource,_id='%i' % table.id)") |
405 | | else: |
406 | | db[table].represent=lambda table:shn_list_item(table,resource='%s' % resource,action='display') |
407 | | search=t2.search(table) |
408 | | # Check for presence of Custom View |
409 | | custom_view='%s_search.html' % resource |
410 | | _custom_view=os.path.join(request.folder,'views',module,custom_view) |
411 | | if os.path.exists(_custom_view): |
412 | | response.view=module+'/'+custom_view |
413 | | else: |
414 | | response.view='search.html' |
415 | | title=s3.crud_strings.title_search |
416 | | return dict(module_name=module_name,modules=modules,options=options,search=search,title=title) |
417 | | else: |
418 | | session.error=T("Unsupported format!") |
419 | | redirect(URL(r=request)) |
420 | | else: |
421 | | session.error=T("Unsupported method!") |
422 | | redirect(URL(r=request)) |
423 | | }}} |
| 32 | The supporting functions are in {{{models/__db.py}}}: |
| 33 | * http://trac.sahanapy.org/browser/models/__db.py |