| 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 |