wiki:BluePrintRESTImplementation

Version 26 (modified by Fran Boon, 13 years ago) ( diff )

crud_strings.table not crud_strings.resource helps ensure no clashes across modules

Implementation for the BluePrintREST:

Model

This is how Module writers need to add tables to their models/module.py:

resource='shelter'
table=module+'_'+resource
db.define_table(table,
                SQLField('modified_on','datetime',default=now),
                SQLField('uuid',length=64,default=uuid.uuid4()),
                SQLField('name'))
# NB Beware of lambdas & %s substitution as they get evaluated when called, not when defined!
db['%s' % table].represent=lambda table:shn_list_item(table,resource='shelter',action='display')
title_create=T('Add Shelter')
title_display=T('Shelter Details')
title_list=T('List Shelters')
title_update=T('Edit Shelter')
subtitle_create=T('Add New Shelter')
subtitle_list=T('Shelters')
label_list_button=T('List Shelters')
label_create_button=T('Add Shelter')
msg_record_created=T('Shelter added')
msg_record_modified=T('Shelter updated')
msg_record_deleted=T('Shelter deleted')
msg_list_empty=T('No Shelters currently registered')
exec('crud_strings.%s=Storage(title_create=title_create, title_display=title_display, title_list=title_list, title_update=title_update, subtitle_create=subtitle_create, subtitle_list=subtitle_list, label_list_button=label_list_button, label_create_button=label_create_button, msg_record_created=msg_record_created, msg_record_modified=msg_record_modified, msg_record_deleted=msg_record_deleted, msg_list_empty=msg_list_empty)' % table)

This is the supporting material in models/_db.py:

from gluon.storage import Storage
crud_strings=Storage()

def shn_crud_strings_lookup(resource):
    "Look up CRUD strings for a given resource based on the definitions in models/module.py."
    return getattr(crud_strings,'%s' % resource)

def shn_rest_controller(module,resource):
    """
    RESTlike controller function.
    
    Anonymous users can Read.
    Authentication required for Create/Update/Delete.
    
    Supported Representations:
        HTML is the default (including full Layout)
        PLAIN is HTML with no layout
         - can be inserted into DIVs via AJAX calls
         - can be useful for clients on low-bandwidth or small screen sizes
        JSON
         - read-only for now

    ToDo:
        Alternate Representations
            JSON create/update
            SMS,CSV,XML,PDF
        Search method
        Customisable Security Policy
    """
    
    table=db['%s_%s' % (module,resource)]
    crud_strings=shn_crud_strings_lookup(resource)
    
    # Which representation should output be in?
    if request.vars.format:
        representation=str.lower(request.vars.format)
    else:
        # Default to HTML
        representation="html"
    
    if len(request.args)==0:
        # No arguments => default to list (or list_create if logged_in)
        if representation=="html":
            list=t2.itemize(table)
            if list=="No data":
                list=crud_strings.msg_list_empty
            title=crud_strings.title_list
            subtitle=crud_strings.subtitle_list
            if t2.logged_in:
                form=t2.create(table)
                response.view='list_create.html'
                addtitle=crud_strings.subtitle_create
                return dict(module_name=module_name,modules=modules,options=options,list=list,form=form,title=title,subtitle=subtitle,addtitle=addtitle)
            else:
                add_btn=A(crud_strings.label_create_button,_href=t2.action(resource,'create'))
                response.view='list.html'
                return dict(module_name=module_name,modules=modules,options=options,list=list,title=title,subtitle=subtitle,add_btn=add_btn)
        elif representation=="plain":
            list=t2.itemize(table)
            response.view='plain.html'
            return dict(item=list)
        elif representation=="json":
            list=db().select(table.ALL).json()
            response.view='plain.html'
            return dict(item=list)
        else:
            session.error=T("Unsupported format!")
            redirect(URL(r=request,f=resource))
    else:
        method=str.lower(request.args[0])
        if request.args[0].isdigit():
            # 1st argument is ID not method => display.
            if representation=="html":
                item=t2.display(table)
                response.view='display.html'
                title=crud_strings.title_display
                edit=A(T("Edit"),_href=t2.action(resource,['update',t2.id]))
                delete=A(T("Delete"),_href=t2.action(resource,['delete',t2.id]))
                list_btn=A(crud_strings.label_list_button,_href=t2.action(resource))
                return dict(module_name=module_name,modules=modules,options=options,item=item,title=title,edit=edit,delete=delete,list_btn=list_btn)
            elif representation=="plain":
                item=t2.display(table)
                response.view='plain.html'
                return dict(item=item)
            elif representation=="json":
                item=db(table.id==t2.id).select(table.ALL).json()
                response.view='plain.html'
                return dict(item=item)
            else:
                session.error=T("Unsupported format!")
                redirect(URL(r=request,f=resource))
        else:
            if method=="create":
                if t2.logged_in:
                    if representation=="html":
                        t2.messages.record_created=crud_strings.msg_record_created
                        form=t2.create(table)
                        response.view='create.html'
                        title=crud_strings.title_create
                        list_btn=A(crud_strings.label_list_button,_href=t2.action(resource))
                        return dict(module_name=module_name,modules=modules,options=options,form=form,title=title,list_btn=list_btn)
                    elif representation=="plain":
                        form=t2.create(table)
                        response.view='plain.html'
                        return dict(item=form)
                    elif representation=="json":
                        # ToDo
                        item='{"Status":"failed","Error":{"StatusCode":501,"Message":"JSON creates not yet supported!"}}'
                        response.view='plain.html'
                        return dict(item=item)
                    else:
                        session.error=T("Unsupported format!")
                        redirect(URL(r=request,f=resource))
                else:
                    t2.redirect('login',vars={'_destination':'%s/create' % resource})
            elif method=="display":
                t2.redirect(resource,args=t2.id)
            elif method=="update":
                if t2.logged_in:
                    if representation=="html":
                        t2.messages.record_modified=crud_strings.msg_record_modified
                        form=t2.update(table,deletable=False)
                        response.view='update.html'
                        title=crud_strings.title_update
                        list_btn=A(crud_strings.label_list_button,_href=t2.action(resource))
                        return dict(module_name=module_name,modules=modules,options=options,form=form,title=title,list_btn=list_btn)
                    elif representation=="plain":
                        form=t2.update(table,deletable=False)
                        response.view='plain.html'
                        return dict(item=form)
                    elif representation=="json":
                        # ToDo
                        item='{"Status":"failed","Error":{"StatusCode":501,"Message":"JSON updates not yet supported!"}}'
                        response.view='plain.html'
                        return dict(item=item)
                    else:
                        session.error=T("Unsupported format!")
                        redirect(URL(r=request,f=resource))
                else:
                    t2.redirect('login',vars={'_destination':'%s/update/%i' % (resource,t2.id)})
            elif method=="delete":
                if t2.logged_in:
                    t2.messages.record_deleted=crud_strings.msg_record_deleted
                    t2.delete(table,next=resource)
                else:
                    t2.redirect('login',vars={'_destination':'%s/delete/%i' % (resource,t2.id)})
            else:
                session.error=T("Unsupported method!")
                redirect(URL(r=request,f=resource))

Controller

If using a single table for a resource, Developers just need to add this to their Controllers to provide all necessary CRUD functions with support for multiple representations:

def shelter():
    "RESTful CRUD controller"
    return shn_rest_controller(module,'shelter')

Views

If using a single table for a resource, Developers don't normally need to create any special views for CRUD. Default ones work fine.

If needing to create custom views (e.g. GIS Layer currently) then can extend these to add extra information in a maintainable way. create.html

{extend 'layout.html'}}
{{try:}}
 {{=H2(title)}}
{{except:}}
{{pass}}
{{include 'key.html'}}
<div class='form-container'>
{{try:}}
 {{=form}}
{{except:}}
  {{include}}
{{pass}}
</div>
<p>&nbsp;</p>
{{try:}}
 {{=list_btn}}
{{except:}}
{{pass}}

display.html

{{extend 'layout.html'}}
{{try:}}
 {{=H2(title)}}
{{except:}}
{{pass}}
{{try:}}
 {{=edit}}
{{except:}}
{{pass}}
<div class='item-container'>
{{try:}}
 {{=item}}
{{except:}}
  {{include}}
{{pass}}
</div>
{{try:}}
 {{=delete}}
{{except:}}
{{pass}}
<p>&nbsp;</p>
{{try:}}
 {{=list_btn}}
{{except:}}
{{pass}}

list.html

{{extend 'layout.html'}}
{{try:}}
 {{=H2(title)}}
{{except:}}
{{pass}}
{{try:}}
 {{=H3(subtitle)}}
{{except:}}
{{pass}}
<div id='list-container'>
{{try:}}
 {{=list}}
{{except:}}
  {{include}}
{{pass}}
</div>
<p>&nbsp;</p>
{{try:}}
 {{=add_btn}}
{{except:}}
{{pass}}

list_create.html

{{extend 'layout.html'}}
{{try:}}
 {{=H2(title)}}
{{except:}}
{{pass}}
{{try:}}
 {{=H3(subtitle)}}
{{except:}}
{{pass}}
<div id='list-container'>
{{try:}}
 {{=list}}
{{except:}}
{{pass}}
</div>
<p>&nbsp;</p>
{{try:}}
 {{=H3(addtitle)}}
{{except:}}
{{pass}}
<div class='form-container'>
{{try:}}
 {{=form}}
{{except:}}
{{pass}}
</div>
{{include 'key.html'}}

update.html

{{extend 'layout.html'}}

{{try:}}
 {{=H2(title)}}
{{except:}}
{{pass}}
{{include 'key.html'}}
<div class='form-container'>
{{try:}}
 {{=form}}
{{except:}}
  {{include}}
{{pass}}
</div>
<p>&nbsp;</p>
{{try:}}
 {{=list_btn}}
{{except:}}
{{pass}}

key.html

<p><b>{{=T('Key')}}:</b><b class='red'> * </b> - {{=T('Fields tagged with a star')}} &#040;<span class='red'> * </span>&#041; {{=T('are mandatory and must be filled')}}.</p>

plain.html

{{=item}}
Note: See TracWiki for help on using the wiki.