All in one forms

June 2, 2010

This’s a quick topic that I felt I should write about today.

  • We should know that request.args(..) is better than request.args[..] why?
    because the former doesn’t raise an exception[error ticket] in case the element with the specified index is not there.
    How could this be important?Shouldn’t I get an error when trying to access something that is not there ? Yes and No ….

    In web2py you could do something like:

    def action():
       if not len(request.args):
           form = SQLFORM(db.table_name)
           if form.accepts(request.vars, session):
                response.flash = 'record created'
        else: # update form
            record = db(db.table_name == request.args[0]).select()
             if len(request.args) and len(records): 
            form = SQLFORM(db.table_name, request.args[0], deletable=True)
            if form.accepts(request.vars, session):
                response.flash = 'record updated'
        records = db(db.table_name.id>0).select()
        return dict(form=form, records=records)
    
  • Now the question is :- do I really have to check for the request.args ?

    In fact in this simple case , I don’t…. unless i’m using request.args[0]
    which shall raise an exception if there’re no arguments…in the request.
    If I’m using request.args(0), I don’t have to check for the len() and again in my simple case .You may need it in other contexts.

  • Now another question: Do I’ve to make a database query like:
     record = db(db.table_name == request.args[0]).select()
    

    and the other check on len(records) in order to make an update form?

     if len(request.args) and len(records): 
            form = SQLFORM(db.table_name, request.args[0], ...)
    

    Ah …. see ? I’d to make sure that request.args in fact of length > 0
    and that request.args[0] is a valid record id in the target table.
    this’s in order to make an update form.

  • Yet another question: Don’t you all find that using 2 forms one for creating and the other for updating forms is a bet overhead [IN THIS SIMPLE CASE]?

    One can argue that it’s important because in the 2nd case of the update form one has to specify the record id , ‘deletable=True’, and ‘upload=URL(…)’ for the cases in which form has image fields.

    But, using request.args() makes things easier

  • In fact I can do something like:
    • ex:
       def index():    
          form = SQLFORM(db.table_name, request.args(0), deletable=True, upload=URL(r=request, f='download'))
          if form.accepts(request.vars, session):
              if not form.record: 
                  response.flash = 'record created'
              else:
                  if form.vars.delete_this_record: # form.vars.delete_this_record is True in case one submits form to delete a record
                      session.flash = 'record deleted'
                  else:
                      session.flash = 'record updated'
                  redirect(URL(r=request, f='index'))            
          records = db().select(db.table_name.ALL)        
          return dict(form=form, records=records)
      
    • form.record is True in case of update form otherwise it’s False.True means it holds the id of the record intended to be updated.
      An update form having second argument as request.args(0) which is None in case there’re no arguments will be equivalent to a create form .
      Interesting right?!!! 😀
    • Warning:
      records = db().select(db.table_name.ALL)  
      

      should be in the end of the function so that when you submit a new record or update/delete a record effects will be reflected directly
      the database query is done after the form submission
      if you put it above all code.. code still works but when inserting a new record you’ve to make an extra refresh to see the effect in the table containing records

    • Why I do redirect after update?
      because after updating a form it keeps the values on it and I don’t like user to see the old records data after deleting a record …
      This has the effect of tempting user to hist submit again causing a ticket to be thrown because he’s trying to update a non existing record.
    • To complete the tutorial, I’ve to provide you with the view file
      {{extend 'layout.html'}}
      
      {{=form}}
      
      <table>
      	<tr>
      	<th>name</th>
      	<th>image</th>
      	</tr>	
      	{{for record in records:}}
      	{{filename, file = db.table_name.x.retrieve(record.x)}}
      	<tr>
      		<td>
      		{{=filename}}
      		</td>
      		<td>	
      		{{=A(IMG(_src=URL(r=request, c='default', f='download', args=[record.x])), _href=URL(r=request, c='default', f='index', args=[record.id]))}}
      		</td>
      	</tr>
      	{{pass}}
      </table>
      
  • Advertisements

    Upload Download files

    April 28, 2010
  • Using multiple tables in the same page as you know requires explicitly adding form name when you accept it
  • suppose a situation in which you need to make multiple upload forms in the same page may be for users to upload their cvs
  • Now you’ve 3 problems :
    • How could you add multiple forms in the same page and in the same time make them work ?
    • How to upload the files (cvs)
    • How to download them
    
    def jobs():
            ...............................................
            ...............................................
            jobs = db(...).select(...)
            for job in jobs:
                # I'll use the same name for form name as well as the field
                formname = "upload_f_%s"%job.id
                form = FORM(INPUT(_type="file",_name=formname,
                              requires=[IS_NOT_EMPTY(),
                                        IS_LENGTH(1048576, 1024, error_message=CustomMessages.UPLOAD_CV_SIZE)
                                       ]
                              ),
                        INPUT(_type='submit'))
            
                if form.accepts(request.vars, formname=formname):
                    stream=eval('form.vars.%s.file'%formname)
                    filename= eval('form.vars.%s.filename'%formname)        
                    file_id = db.cvs.insert(cv=db.cvs.cv.store(stream,filename=filename),
                                        job_title=job.title,
                                        job_id=job.id)
                
                    # add permissions for that file    
    
                    # do other stuff       
    
  • As you see , I made a form with a field of type ‘file’ and form name is variable based on the job_id , C.Vs are going to be uploaded
    this helps making multiple forms per page and in the same time every form has a unique name that will be used when accepting the form .
  • Now as you may have guessed the form containing a ‘file’ field will have (when being accepted) :
    form.vars.field_name.file  -> get the reference to the file
    form.vars.field_name.filename  -> get the file name
    

    Now you can easily do something like:

    stream=form.vars.field_name.file
    filename= form.vars.field_name.filename
    

    Now you can store your file name into the database

    db.cvs.insert(cv=db.cvs.cv.store(stream,filename=filename),
                                        job_title=job.title,
                                        job_id=job.id)
    
    

    see the :

    db.table_name.field_of_type_upload.store()
    
  • Congratulations !!! you’ve uploaded your files

  • Now for downloading files you can do :
    def download_cv():
        file_id = request.args(0)
        import cStringIO 
        import contenttype as c
        s=cStringIO.StringIO() 
        
        (filename,file) = db.cvs.cv.retrieve(db.cvs[file_id].cv)
        s.write(file.read())  
        response.headers['Content-Type'] = c.contenttype(filename)
        response.headers['Content-Disposition'] = \
                    "attachment; filename=%s" % filename  
        return s.getvalue()
    
    

    Now you can make another function that list all cvs available with a link for downloading them as follows:

    def list_Cvs():
        cvs = db(db.cvs.id>0).select(db.cvs.cv) # db.cvs.cv is of type 'upload'
        urls = [A('Download', _href=URL(r=request,\
                                                   c='files',
                                                   f='download_cv', args=[row.id]
                     )) for row in cvs]
        return urls
    
  • what is the benefit of writing my own download function ?
    This enables you to write your own authorization logic before allowing a user to download a file

  • Now what if I just don’t want to write my own download function and use web2py facilities for that matter ?

  • You can use response.stream(), or response.download() functions.
  • What is difference between them?

  • In fact response.stream is the main download function that takes care of streaming files.
    response.download() calls response.stream() to do the streaming stuff.
  • response.download() has its arguments as request, db and is used for downloading files by their names, while the name is the name of the file in the database.
                def download():
                    return response.download(request, db)
            #downloads from http://..../download/filename
    

    response.download() takes request to extract from it the file name stored in [request.args]

    download function is mainly used by SQLFORM to update a record that includes an upload field that is used to upload an image, in this case response.download() gets the file (image) to be displayed when updating this record.

    form = SQLFORM(db.my_table, record, upload=URL(r=request, c='default', f='download'))
    
  • Suppose you want to stream an MP3 file, you can do something like:

    Somebody asked how to stream an mp3 file from the uploads files directly:
    def get_my_file():
          filename=request.args[0]
          path=os.path.join(request.folder,'uploads',filename)
          response.headers['ContentType'] ="application/octet-stream";
          response.headers['Content-Disposition']="attachment; filename="  
    +filename
          return response.stream(open(filename),chunk_size=4096)