Validators between classical and professional usage

April 29, 2010

Introduction
Web2py validators work at the form level, not on the database level
It's recommended though to make both validation at the form level and database level.

  • You can add validators whereever you want but you’ve to pay attention for the logic.
    you can make:

    
    db.define_table('my_table', Field('my_f', requires=[..]))
    # or  inside the same model after the definition of the table :
    db.define_table('my_table', Field('my_f'))
    db.my_table.my_f.requires = [....]
     # you may even do this in your controller
    db.my_table.my_f.requires = [....]  
    form = SQLFORM(db.my_table)
    ......
    

    Things to note though that making adding validators after defining the table or before defining the form in your controller is that in those 2 particular situations, you’re really overriding any other validators on this table field defined in the model, either within the table definition or after it .

    db.define_table('my_table', Field('my_f', requires=[set_no_1]))
    db.my_table.my_f.requires = [set_no_2]  
    

    In the example above, set_no_2 of validators is the one that will actually work.

    To overcome this situation, you may make something like:

    db.define_table('soso', Field('lolo'), Field('moon', requires=[IS_NOT_EMPTY()]))
    db.soso.moon.requires.append(IS_NOT_IN_DB(db, db.soso.moon))
    

    Wow !!! then you can append to the list from anywhere and it will work.

  • Now one can wonders , so what’s the catch ?

  • The catch is that making something like :
    db.my_table.my_f.requires = [IS_IN_DB(...)]
    

    will prevent this field from being represented as a combo box, for this to be achieved you’ve not to make a list

    db.my_table.my_f.requires = IS_IN_DB(...)
    

    in this case you can’t add other validators and any trial to override the validators will ruin the combo box widget.

  • The good news is that there’s a solution 😀 which is ‘using widgets’, look ath this piece of code:
    b.define_table('soso', Field('lolo'), Field('moon', widget=SQLFORM.widgets.options.widget, requires=[IS_IN_DB(db, db.country.name), IS_UPPER()]))
    

    What I did was to force the usage of the options widget, and in this case it will work fine and the other validator will also work, and the value to be entered in database is the the uppercase value of the one you’ve chosen.
    WARNING: IS_IN_DB() MUST be the first in the list, for this to work.


  • IS_IN_DB() and IS_NOT_IN_DB() logical issue

    when using either of the 2 validators, you have to take care of something , which is that you can’t do something like:

    db.define_table('country', Field('name', requires=IS_NOT_IN_DB(db, db.country.name)))
    

    what you want to do is making the country name unique, and not to allow duplication.
    Let’s look more precisely about what you’ve done above.
    requires=IS_NOT_IN_DB(db, db.country.name)
    can you really use db.country.name before the country table is really defined ? You can’t and thus in this case you’ve to move your validation to be after the table definition , something like:

    db.define_table('country', Field('name'))
    db.country.name.requires= IS_NOT_IN_DB(db, db.country.name)
    

    IS_IN_DB() or IS_IN_DB() classical usage
    The same logic goes for both, so I’ll concentrate on one the other will be almost the same, except for the functionality ofcourse.

    db.define_table('category', Field('name'))
    db.define_table('recipe', Field('category', 'reference category'))
     db.recipe.category.requires=IS_IN_DB(db,'category.id','category.name')
       # or
    db.recipe.category.requires=IS_IN_DB(db,db.category.id,'category.name') 
    

    The parameters are:

    • a set of records
    • ‘category.id’ (string) or db.category.id (not a string) describes how the field will be represented in the
      database [this is the value that will be actually sent when accepting a SQLFORM that is using this table]
    • ‘category.name’ describes how the field will be represented to the
      user [Just a representation] and you’ll end up with a combo box displaying the name while accepting the form will send the id to database.
      name is a valid field name in the same table.
    # model
    db.define_table('table_one', Field('name'))
    db.define_table('table_two',Field('a', requires=IS_IN_DB(db, 'table_one.id', '%(name)s')))
    
    # controller
    def index():
        form = SQLFORM(db.table_two)
        if form.accepts(request.vars, session):       
            print form
        return dict(form=form)
    

    supposing table_one has only one record {id=1, name=’hamdy’}
    the form will display a drop down box with one item “hamdy”
    but upon accepting form the id (1) will actually be stored in db

    print form will give you something like:

    <form action="" enctype="multipart/form-data" method="post">
    .........................................................
    <select class="string" id="table_two_a" name="a"><option value=""></option><option value="1">hamdy</option></select></form>
    .........................................................
    

    In fact to be honest what really got to the database in table_two is just an id.
    To make things more professional you’ve to make this id refer to the actual record in tabl_one not just an id.
    To achieve this and test it , do something like:

    # add to your model :
    db.define_table('table_three',Field('a', 'reference table_one', requires=IS_IN_DB(db, 'table_one.id', '%(name)s')))
    
    # controller
    def index():
        form = SQLFORM(db.table_three)
        if form.accepts(request.vars, session):       
            print form
        return dict(form=form)
    

    Now when you go to the appadmin interface to check you database,
    you’ll find that the id in the record is a link to the actual record in the other table and obviously this’s what you wanted from the beginning a foreign key.
    In this case you can add to the field ‘a’ in table_one : ondelete=’CASCADE’ so that when the record in a deleted , all other records in other tables referencing it will be deleted automatically and this is a thing you WILL ALWAYS need, otherwise you’ll suffer a severe headache trying to trace unused records and delete them manually
    in separate database queries.


    IS_IN_DB() professional usage

    • making something like:
      db.define_table('table_two',Field('a', requires=IS_IN_DB(db(db.table_one.name.like('h%')), 'table_one.name')))
      

      you’ll get a combo box with names in your table that starts with ‘h’ .
      choosing one of them and submitting the form will submit its id
      you may even do something like:

      db.define_table('table_two',Field('a', requires=IS_IN_DB(db(db.table_one.name.like('h%')),                       'table_one.id', 'Name: %(name)s, id: %(id)s')))
      

      Note that :

       'Name: %(name)s, id: %(id)s')
      

      is just a string that looks similar to ‘Name:….., id:….’ :

      Name:hamdy, id:1
      Name:aaaa, id:2

      and as I said before, submitting it will submit the id .

      The bottom line here :

      • You just can play with the presentation [how field looks like for user] while the data that will enter the database is different.
      • You can use a query inside IS_IN_DB(query, presentation) and the query is just logically representing a WHERE clause of database query

    Another Professional usage for IS_IN_DB()

  • How to use IS_IN_DB() to make records entered by a user are unique
    but in the same time those records can be entered again but by another user ?
    • Using the web2py example, we have 2 tables, “owners” and “dogs”. The “dogs” table has a many 2 one relation to owners.
      How can I create a constraint/ validator that allows the owner to give
      his dogs unique names? I cannot use IS_NOT_IN_DB because it is is
      database wide, I would like to have duplicate names in the table, but
      not allow duplicate names for a given user. Example, an owner called
      Voltron can have 2 dogs called “skipper” and “lassie” but not
      “skipper” and “skipper”, but another owner called ‘Optimus Prime’ could
      also have a dog called “skipper” or “lassie”
      Was that clear? Any ideas?

      IS_NOT_IN_DB(db(db.dog.owner==owner_id),db.dog.name) 
      

    What I wanted to show you in the previous section is that you can do things using many ways and to ensure you understand the :
    IS_IN_DB() and IS_NOT_IN_DB() and that they can be passed (db) as the first parameter or passed a set of records using db(my_query)
    then the 2nd argument is a database field that either written as :
    'my_table.my_field' as a string or db.my_table.my_field


    Yet another professional usage for IS_IN_DB()

    How to make a data set of some records in a table with appropriate permissions then requires user to choose among them ?

    # Model
    db.define_table('company', ......)
    db.define_table('news',
                    Field('company_id', 'reference company'),                 
                    .....................   
                    migrate=migrate
                    )
    
    db.news.company_id.requires = IS_IN_DB(db(auth.accessible_query(Permissions.OWNER,db.company,auth.user.id)), db.company.name) if auth.is_logged_in() else None
    # db.company.name -> to display company name instead of company id
    

    If you’ve a user who is a company OWNER or several companies OWNER
    then when he ever wants to enter news for one or more companies that he owns, the form should allow him to choose among those companies and make him choose which one to enter news for.

    db(auth.accessible_query(Permissions.OWNER,db.company,auth.user.id
    

    returns records from company db.table that the current logged in user [auth.user.id] has OWNER permission on them
    In fact permissions are set based on groups so what this code really does is to check whether the current user belongs to a group that has OWNER permissions on some records of the table db.company and return those records

    the reason for requires=….. if auth.is_logged_in() else None is that when a user is not logged in the auth.user is not there so it’s None, and that causes the application to fail because auth.user.id is not there [None has no id attribute] so simply I made that simple check knowing that this table is used for forms that requires a user to login.


    Now time for another neat trick that web2py permits
    In fact, you can make validation on a field based on another’s field’s value that is submitted from a form !!! on the fly.
    Consider the following example:

    db.define_table('my_table', Field('category', requires=IS_IN_SET(['url_required', 'url_not_required'
                                ]
                       )
                                 ),
                                 
                           Field('url', requires = IS_EMPTY_OR(IS_URL()) if request.vars.category == 'url_not_required' else IS_URL()
                                 ))
    

    Great .. .right ? 😀

  • In fact IS_IN_SET can be a list of say … records ids and in the same time having labels [aliases] to be human readable
    so making something like:

    db.define_table('my_table', Field('toto', requires = IS_IN_SET([1, 2, 3], labels=['one', 'two', 'three'])))
    

    will yield a dropdown menu with the values[ ‘one’, ‘two’, ‘three’] but in fact what is being sent to database is the integers (ids) [1, 2 or 3]

  • You can even use IS_IN_LIST() with dates, … say for example you’ve a field of type ‘datetime’ then you want users to enter specific dates in it, so you can do something like:
    db.define_table('my_table', Field('date', 'datetime'))
    db.my_table.date.requires=IS_IN_SET([
      datetime.date(1492,10,12),
      datetime.date(1492,11,02),
      datetime.date(1492,11,27)])
    

    Now user will get a drop down menu of the specified dates .


  • Another neat usage for IS_EMPTY_OR() or IS_NULL_OR() [they’re the same] is when you have a table with a field that refers to another table.
    in this particular situation the referencing field must have a value that is equivalent to an existing record id on the referenced table.
    and because it’s difficult to remember the ids or even to know it, you should use IS_IN_DB() validator to have a drop down menu to choose from it . And as you all know, you can format the value in drop down menu to display names instead of ids and what is really going to be sent to server and database is the id.

    db.define_table('cat', Field('category'))
    db.define_table('my_table', Field('category', 'reference cat', requires=IS_IN_DB(db, db.cat.id)))
    # use 'reference cat' or db.cat without single quotes
    

    Now what if you want to have the db.my_table.category set to a category id or if there’s no category you just want Null in this database field or you want just a specific default id to be stored if user let this field empty.
    In this case you use : IS_EMPTY_OR(….., null=) so I can have something like :

    # controller
    def index():
       form = SQLFORM(db.my_table)
       if form.accepts(request.vars, session):
           response.vars = T('yes')
       return dict(form=form)
    
    #model
    db.define_table('cat', Field('category'))
    db.define_table('my_table', Field('category', 'reference cat', requires=IS_EMPTY_OR(IS_IN_DB(db, db.cat.id), null=None)))
    

    In this case you can just let this field empty and not to choose a category and in this case will be stored in this field.
    If you want something other than such as a default category id, you can use :

    requires=IS_EMPTY_OR(IS_IN_DB(db, db.cat.id), null=1))
    # 1 should be valid record in db.cat
    <strong>Warning:</strong> if the value assigned yo null= dosn't exist , you 'll get an error trying to access database table via appadmin interface, it should be a valid record id in the referenced table.
    
    

    oh, don’t forget to check my discussion about the IS_EMPTY_OR() validator in Web2py Zen


  • How could I get rid of some validators that I don’t like in some actions
    [controller functions] ?

    anywhere in your action before defining the SQLFORM, just do:

    db.my_table.my_field.requires=[] 
    

  • How could one validate against a set of translated values?

    you do it the normal way:

    db.define_table('table_two_two',Field('a', 'string', requires=IS_IN_SET([T('v'), T('d')])))
    #requires=[IS_IN_SET(..)] -&gt; No combobox
    #requires=[IS_IN_SET(..)] -&gt; combobox
    

    Date & Time Validators

    Now we've IS_DATE, IS_TIME, IS_DATE_TIME
    
    db.define_table('a', Field('date', 'datetime'))
    db.a.date.requires=IS_DATETIME('%Y-%m-%d %H:%M:%S-%p', error_message=...)
    

    and the date field b will be processed in the desired format, specified by the custom format string that you can edit as you like :

    ('%Y-%m-%d %H:%M:%S-%p'
    

    For Numbers you use

    IS_INT_IN_RANGE(min,max)
    

  • For Telephones:
    you may use something like :

    IS_MATCH(&quot;^1+\d{3}\-?\d{3}\-?\d{4}$&quot;)
    # or for european something like
    IS_MATCH(&quot;^\+?[\d\-]*$&quot;) 
    
  • How do I validate a number that must always consist of 5 numerals including numbers starting with a 0? Here are examples are 12345, 01234, 89765. I used IS_INT_IN_RANGE but the numerals that start with 0 did not validate. ?

    You need to store as ‘string’ with length=5 and require

    IS_MATCH('^\d{5}$') 
    

    Telephone numbers should be stored as strings otherwise numbers starting with 0 will be treated as octal numbers.


  • validation not working on FORM field why?

    Now when talking about FORM validation it’s the same as the SQLFORM validation , though you’ve to notice something :
    Take care and give form field a name in order for validation to work
    ex:

    # validation here won't work, although the form submission seems to work just fine
    form = FORM(INPUT(_type='text', requires=IS_LENGTH(minsize=3, maxsize=8)),
                    INPUT(_type='submit')
                    )
        if form.accepts(request.vars, session):
            response.flash = T('hey')
        return dict(form=form)
    

    In order for the previous example to work, field needs to have a name
    so that requst.vars contains request.vars.field_name and the validation works.
    So you need change the form definition into something like:

    
    form = FORM(INPUT(_name='name', _type='text', requires=IS_LENGTH(minsize=3)),
                    INPUT(_type='submit')
                    )
    

  • You can use IS_LIST_OF() validator to validate a list of values

  • Q: I’ve my own function that does something complex and I want to use it for values submitted from a form to process that data and make some operation on it before actually being saved to database.
    What can I do?

    Ans:

    Suppose you’ve a function like:

    def my_owsome_func(x):
        return return x.replace('-', '_')
    

    Now I’m having in my model:

    db.define_table('my_table', Field('my_url', requires=IS_URL()))
    

    and in my controller:

     form = SQLFORM(db.my_table)
        if form.accepts(request.vars, session):
            response.flash = T('yes')
        return dict(form=form)
    

    Now I want when user inputs a URL, the my_owsome_func() processes the URL before actually saved into database then replace every occurrence of ‘-‘ with ‘_’.

    Think a little … what is the thing that processes data before actually being saved into database ?
    Yes … Validators
    Now if I were able to use my_owsome_func() in a validator, this would be awsome ..!!!!
    Yes you can :
    you can make your custom validator like:

    from gluon.validators import Validator
    class MY_CUSTOM_VALIDATOR(Validator):
        def __init__(self, f):
            self._f = f
        def __call__(self, value):
            return (self._f(value), None)
    

    as you can see a typical validator should (but not required to) extend the Validator class in gluon.validators .
    gluon.validators.Validator includes a formatter() function that you might need in formatting input.
    In fact few validators use formatter() any way, and you’re not going to use it at most conditions too.

    A typical validator includes __init__() and __call__() functions, where __init__() is called once validator class instantiated and is used to initialize the object .
    __call__() is called at the end of the execution and acts upon a value
    so validator needs to be called in this way :

    my_validator(value_to_check_against)(value_to_check)
    

    (value_to_check_against is saved as an attribute using the __init__()
    and value_to_check is called by __call__()

    A typical validator should return (value_to_check, None) if validation succeeded and (value_to_check, error_message) if not
    In our case we just made a validator returns (value_after_processing, None) … where None indicates validation succeeded
    We don’t need to have an error message because we use a validator to process data that should be correct .
    How can I guarantee that data is correct ?
    using another validator that is used before my custom one, so that we end up using one validator for data integrity and the other to process data before actually saving into database.

    In our example, we pass function to the validator and in the __call__() we use it to process data and return result.



  • CRYPT() validator

    Not used By default for password field in auth_user table [It should]
    to use it, You need to do somewhere in your model ,something like:

    auth.define_tables() 
    db.auth_user.password.requires=CRYPT()
    

    auth.define_tables() is responsible for creating the default tables each application should have, one of them ofcourse is auth_user and thus :
    db.auth_user.password.requires=CRYPT() should come after it not before it.

    Warning:
    Doing that when you’re already having passwords saved in database means that they won’t work any more unless you made once and only once :

    for row in db().select(db.auth_user.ALL):
       row.update_record(password=CRYPT()(row.password)[0])
    

    Quickly, getting a table of records

    April 28, 2010
  • Of course you know that something like:
    rows = db(db.jobs.id > 0).slect()
    

    yields a Rows object that can be returned using :

    return dict(rows=rows)
    

    and in your view making something like:

    {{=rows}}
    

    will get you a table , yes an HTML table created for you on the fly

  • The problem with this approach is that when you need more customization like decorating your table using java script or doing any other customization you’ll have to create the table manually in the view doing something like:
    <table class="blahblahblah">
    <th> {{T("Title'')}} </th>
    <th> {{T("Location'')}} </th>
    {{for row in rows:}}
    <tr> 
    <td>{{row.title}} </td>
    <td>{{row.location}} </td>
    </tr>
    {{pass}}
    </table>
    

    boring is not it ?

  • We can make another hackish trick which makes our lives easier:
    since Rows object has an attribute called colnames containing the column names of the rows being returned and since column names consists of table_name.field_name:
    you can do something like :

    x = db(db.jobs.id>0).select()
    [column.split('.')[1] for column in x.colnames]
    ['id', 'title']
    

    Now you can do :

    ---- mytable.html ----
    {{def mytable(records, class_name=''):}}
    <table class="{{=class_name}}">
    {{for col in [column.split('.')[1] for column in x.colnames]:}}
    <th> col</th>
    {{pass}}
    {{for r in records:}}
    <tr>{{for k,v in r.items():}}
    <td>{{=v}}</td>
    {{pass}}</tr>
    {{pass}}
    </table>
    {{return}}
    
  • After that all you need to do when ever trying to make a table is to
    import mytable.html in your view with

     {{include 'mytable.html'}}
    

    and use it

    {{mytable(db(...).select(), 'class_name')}}
    

    then you can personalize your table as you want and reuse it.


  • Cool tools for web development

    April 28, 2010

    txt2tags   tinymce  pydns   libgmail  iodbc  win junction [sym links for ntfs on windows]   easy eclipse   Testing tools  yaml  web2py user group search plugin    ulipad   sphinx   firedaemon  edna [mp3 server] flot  sqldesigner and you can check it out here   
    Free layouts   JQueryUI  Breve   send sms  open laszlo  (disqus) An interesting comment system for your site    DotNet Nuke    helpful firefox plugin for developers   Merbivore   Cheetah   How to know visitor’s country  Python captcha extjs 
    JQuery live search 1 and hereopenlayers Change image to icon 
    yslow webx css library Rat Proxy


    helpful tutorials

    April 28, 2010
  • Python fastcgi wsgi and lighthttpd
  • how to integrate openid as your login system
  • In case you want to make an ORM or understand it, you’ve to read much about DBMS :
    you may read about CREATE TABLE dialects:

  • DirectToDiskFileUpload
  • optparse
  • Conditional GET
  • Java script Timeout
  • web2py on webfaction
  • How To Create a User-Defined Service on MS windows

  • REST introduction
  • jsonp
  • access-control-allow-origin-response-header
  • datetime tutorial
  • jquery-tutorial-passing-input-arrays
  • uri-schemes
  • Python eval()
  • Check that
  • Caching
  • python standard library
  • JQuery cheat sheet
  • pubsub concept
  • decorators_rest
  • UsingExtensions
  • MercurialHosting
  • many-to-many
  • Object-relational_mapping
  • Database_abstraction_layer

    <a

  • href=”http://www.agiledata.org/essays/mappingObjects.html”>mappingObjects

  • Setting a form field default value

    April 28, 2010
  • Many times and in certain situations , one needs to set a default (certain) value for a form filed

    suppose you’ve :

  • #in model:
    db.define_table('my_table', Field('my_field', 'string'))
    
    #in controller:
    form = SQLFORM(db.my_table)
    
    if form.accepts(...):
       print form.vars.my_table
    
  • Now I want the ‘my_filed’ to have a default value, may be : ‘Hamdy’
    what should I do ?
  • In fact you should differentiate between 3 suitations:
    • you need the form always to display and accept ‘Hamdy’ as a default value if user didn’t enter his custom text.
      This can be achieved by :

      db.define_table('my_table', Field('my_field', 'string', default='Hamdy'))
      

      or:

      form.vars.my_field.default = 'Hamdy'  # b4 defining the form
      form = SQLFORM(db.my_table)
      
      if form.accepts(...):
         print form.vars.my_table
      
    • The second situation is that the default string is variable depending on some conditions in this case, one should always use :
    • form.vars.my_field.default = 'Hamdy'  # b4 defining the form
      form = SQLFORM(db.my_table)
      

      which is suitable for almost all conditions

    • The 3rd situation is when you have a classic form, may be using :
      form = FORM()
      

      you can use Field(”), default=….) in it but what if the default value is variable ?
      you can’t set the value using db. …… since the form is not dealing with a database table so what can you do ?

      In this case make something like :

      form = FORM()
      form.vars.field = 'Hamdy'  # after the definition of the form
      if form.accepts(..., ...):
        # do something
      

      This technique is also very useful for SQLFORM that is dealing with a database table but in a certain situation you added to it an additional field (field added to form not to db table).
      In this case you can still play with it using this method.

  • To this extent, I guess you’re almost get how to deal efficiently with web2py forms, but I still have more 🙂
    One another interesting problem arises when you set in your model a field to be readable=False, writable=False and this’s a common situation when using a timestamp (creation_time) field in youer database table, in such a case you do something like :

    db.define_table('my_table', Field(...), Field('timestamp', 'datetime', readable=False, writable=False, default=request.now))
    
  • Now when ever trying to make a SQLFORM out of this table, user will not see the timestamp field , it will be updated though when submitting data.
    In fact readable=False, writable=False prevents this field to be a prt of the form.
    So having in my model :

    db.define_table('toto', Field('lolo'), Field('soso', readable=False, writable=False, default='hamdy'))
    

    and in my controller :

    def index():
    
        form = SQLFORM(db.toto)
        if form.accepts(request.vars, session):
            print form.vars
            form.vars.lolo = 'yet another value'   # -> not working
            response.flash = T('form accepted')
        return dict(form=form)
    
  • user will see one field ‘lolo’ and when trying to print the form.vars upon accepting it, you’ll find no existence for the ‘soso’ field
  • trying to set this a value for this field upon accepting the form using

    form.vars.lolo = 'yet another value' will not work also
    

    database will have the default value which is ‘Hamdy’ for this field for every new record you try to create.

  • what is going to work is to set the default value before defining the form at the database level not on the form level , Since form doesn’t really include this particular field :
      db.toto.soso.default = 'Another value'   #working
      form = SQLFORM(db.toto)
    
  • At this level there’s still no problem, the problem in fact will appear whenever you try to make additional check on this particular field value
    upon accepting the form.
    Say for example you made :

      db.toto.soso.default = session.name  #working
      form = SQLFORM(db.toto)
    
  • Now suppose you want to make additional check for this session.name variable to make sure it’s not just a garbage.
    In such situations you may use the onvalidation function while accepting the SQLFORM form so that more validation can be done

    def check(form):
        if form.vars.soso == 'Not hamdy':
            print 'Not hamdy'
            form.vars.soso = 'Not Not Not hamdy'
    
    def index():
        db.toto.soso.default = 'Not hamdy'   #working
        form = SQLFORM(db.toto)
        if form.accepts(request.vars, session, onvalidation=check):
            response.flash = T('form accepted')
        return dict(form=form)
    
  • Now as you see check() function is evaluated but does nothing since there’s no existence for a form.vars.soso since the field itself is not a part of the form

    SO what to do?

    It’s simple , in the definition of SQLFORM you can choose what fields you want in the form
    so you simply do something like:

    def check(form):
        if form.vars.soso == 'Not hamdy':
            form.errors['lolo'] = "There's an error"
                    
    def index():
        db.toto.soso.default = 'Not hamdy'   #working
        form = SQLFORM(db.toto)
        form.vars.soso = 'Not hamdy'  # it's a python magic
        if form.accepts(request.vars, session, onvalidation=check):
            response.flash = T('form accepted')
        return dict(form=form)
    
    

    form.vars.soso = ‘Not hamdy’ is just a python magic, being able to create and set an object attribute on the fly and it really just a variable
    not related to the form .
    we just created it to check the value.

  • The last problem that may face a user is that he/she may have to display this field in some certain situations but in others it should be hidden.
    Now it’s time for a user to decide what is the default case should it be hidden or shown by default.
    If it’s hidden by default you can show it in SQLFORM by using

     form = SQLFORM(db.toto, fields=['lolo', 'soso'])
    

    By using fields=[] you can choose what fields to display

    and if the the other case is the default, you can hide the field using

    db.soso.readable = False
    db.soso.writable = False
    form = SQLFORM(db.toto)
    

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