Loading html elements dynamically with web2py and ajax

Put this code in a view /views/default/index.html

only works if you have static/js/web2py.js, normally comes with welcome app

<!-- begin -->
<button class="load_content" data-url="{{=URL('default', 'otherthing1')}}" data-target="ajax_container"> Click to load content1 </button>
<button class="load_content" data-url="{{=URL('default', 'otherthing2')}}" data-target="ajax_container"> Click to load content2 </button>
<button class="load_content" data-url="{{=URL('default', 'otherthing3')}}" data-target="ajax_container"> Click to load content3 </button>
<button class="load_content" data-url="{{=URL('default', 'otherthing4')}}" data-target="ajax_container"> Click to load content4 </button>


<div id="ajax_container"> <!-- CONTENT COMES HERE --> </div>

<script>
$(function () {
   $('.load_content').on('click', function (e) {
        elem = $(this); // elem = $(e.target)
        url = elem.attr("data-url");
        target = elem.attr("data-target");
        web2py_ajax_page("GET", url, "", target);
        return false; // e.preventDefault()
      });
})
</script>
<!-- end -->

When the user clicks on buttons, the content is ajax-loaded in to ajax_container

web2py routes

customizing routes in web2py

web2py comes with defaults for url routes, the default configuration uses the following pattern

http://host:port//<controller name>/<function name>/args/?vars=value

Sometimes we need to change it to a better and beauty URL like

http://host:port/<controller name>/<function name>
or even
http://host:port/<function name>

thats how to to it

Put those lines in /web2py/routes.py - if you have an application called myapp this would be

# -*- coding: utf-8 -*-

routers = dict(
# base router
    BASE=dict(
        default_application='myapp',
    ),
    # app specific router
    myapp=dict(
        default_controller='home',
        default_function='index'
    )
)

logging = 'print'

# routes_onerror = [
#     (r'myapp/404', r'/myapp/static/fail404.html'),
#     (r'myapp/*', r'/myapp/static/fail.html'),
#     (r'*/404', r'/myapp/static/cantfind.html'),
#     (r'*/*', r'/myapp/error/index'),
# ]

error_message = ('<html><body>'
                  '<strong>ERROR DETECTED </strong>'
                  '<h1>%s</h1>'
                  '</body></html>')

error_message_ticket = ('<html><body><h1>Internal error</h1>Ticket issued:'
                         '<a href="/admin/default/ticket/%(ticket)s"'
                         ' target="_blank">%(ticket)s</a>'
                         '<h1>ERROR DETECTED</h1>'
                         '</body></html>')

Custom validator for web2py forms

web2py allows us to write custom form validators, by pattern validators in web2py are UPPERCASE

Hi Pythonista! I know that you are now wondering why does not follow PEP8 name convention? Ok, this is the web2py way, it breaks PEP8 a bit, but it has a good reason The naming convention is for preventing conflicts between objects named form and FORM helper class

Validators templates

There are two types of validators

  • VALIDATOR: It validates if some data is valid or if follow some pattern

  • TRANSFORMATION; It only takes the entered value and returns the transformed value

The patterns

class VALIDATOR(object):
    def __init__(self, error_message="SOMETHING WRONG"):
        self.error_message = error_message

    def __call__(self, value):
        error = None
        # CONDITION COMES HERE
        if "ERROR":
            error = self.error_message

        # IF error != None - value is invalid 
        return (value, error)

class TRANSFORMATION(object):
    def __init__(self, search, replace):
        self.search = search
        self.replace = replace

    def __call__(self, value):
        error = None
        try:
            # TRANSFORMATION COMES HERE
            value = value.replace(self.search, self.replace)
        except:
            error = "Not possible to transform"
        return (value, error)

How to write my own validator

Follow the patterns above, lets see some example

Validate if a zip-code starts with "051"

By some reason our system does not allows registering from other regions out of "051" zip code

class IS_ALLOWED_ZIP_CODE(object):
    def __init__(self, zip_area, error_message="Zip code not allowed"):
        self.zip_area = zip_ares
        self.error_message = error_message

    def __call__(self, value):
        error = None
        value = value.strip()
        if not value.startswith(self.zip_area):
            error = self.error_message
        return (value, error)

Now you can use this in your models

db.define_table("address",
    ...
    Field("zipcode", requires=IS_ALLOWED_ZIP_CODE("051"), notnull=True)
    ....)

Simply like that! now your forms will fire the "Not allowed Zip Code" when users tries to input a zip code as "04509-890"

Transform some text

class REPLACE_TEXT(object):
    def __init__(self, search, replace):
        self.search = str(search)
        self.replace = str(replace)

    def __call__(self, value):
        error = None
        try:
             value = value.replace(self.search, self.replace)
        except:
            error = "Error replacing"
        return (value, error)

In the same way you now replace values in your forms
Example: User enters latitude, longitude with "," and you replace with "."

replace_comma_with_dot = REPLACE_TEXT(",", ".")
db.define_table("address",
    Field("latitude", requires=replace_comma_with_dot),
    Field("longitude", requires=replace_comma_with_dot)
)

HOW IT WORKS?

web2py forms and DAL validate_and_* methods has a step where it takes all values in requires list
for every field in the table, so it takes the entered value and do a call to the validator class
as you can see, the validator implements the __call__ magic method, which means that it will execute when
the instance of the class are called. Yes, instances are callables

class Foo(object):
    def __call__(self, *args):
        print("I've been called with some args %s" % str(args))

>>> foo = Foo()
>>> foo("web2py", "rocks")  # we are calling the instance directly
I've been called with some args ('web2py', 'rocks')

also, is it possible to do some validation and transformation using compute for fields and form events as onvalidation and onaccepts

Thats it!

web2py dynamic queries with reduce

Sometimes, specially when creating search engines, we may need to build the queries dynamically. web2py’s DAL is flexible and permits you to use Python reduce for this job.

queries=[db.table]
if arg1 == "x":   
    queries.append(db.table.field == x)
if arg2 == "y": 
    queries.append(db.table.otherfield == y)
# many conditions here....
query = reduce(lambda a, b:(a & b), queries)
rows = db(query).select()

web2py on PythonAnywhere

Web2py

web2py is a powerful and easy to learn Python web framework, it is a full stack framework which means that you are going to find built in features for almost aeverything you need on web development.

web2py is safe, fast, smart and it simply lets you to get things done!

(of course this is a biased oppinion :P )

Python Anywhere

Python is great! everybody loves it. Now imagine the possibility to have your Python Environment on the cloud, on that environment Python from version 2.5 to 3.2, in the same environment you have a mysql shell and a bash shell where you can use git, hg, unzip, tar, wget and many other *unix features and additionally you have a very nice cron-like scheduler to program python/sh tasks.

The best feature ever is the ability to share the same Console with many people and the ability to save the console session even if you loggout or need to switch form computer to tablet.

Thats http://www.PythonAnywhere.com ! The best place to learn, test, develop with your team and of course, the best place to host your websites and services!

web2py app on PythonAnywhere

  1. Register on PythonAnywhere for a free or payed account
    Python Anywhere Offers some plans:
    PAPlans
  1. On Dashboard create a new web2py application PAWEB
  1. Choose an admin password and click next to wait while your web2py app is created This Process can take a bit of time, PA will download and unzip a new web2py stable source
    and also it will configure your wsgi.py pointing to your web2py root folder.

When it finished you will see this screen: PANok

Now you have a running web2py application running on http://yourname.pythonanywhere.com

Your appp should look like this:


PythonAnywhere tabs

  1. Files tab - you can browse, edit, create, upload and connect to a DropBox account PANtabs
  1. web tab - You can reload your apache server, see your logs or edit your wsgi.py file panweb
  1. Schedule tab - Here you can schedule task to run hourly or daily, tasks are sh or python scripts PANTASK
  1. Mysql tab - here you can create a database and change passowrd, also check credentials for connection panmysql
  1. Consoles - the most powerful feature you can start and share consoles panconcole

Lets code!

Now that your are familiar with PythonAnywhere we can create our app, I am going to show how to do that using two approaches

Using web2py web IDE for coding and management

  1. Go to your app https://yourname.pythonanywhere.com/admin - Note the https, it is necessary to access the web2py admin

Thats the web2py admin interface, put the password you set on app install step

  1. Create a new simple app named app1

  1. Web2py will let you admin your app, edit files, create database models using the web interface

  1. Click in edit next to db.py under models section

Look for this line of code db = DAL('sqlite://storage.sqlite') now we are going to change it
as it is connecting to a sqlite database, we want to change it to our mysql database

You can go to MYSQL tab on PythonAnywhere dashboard and create a new Mysql Databse, and also set a new password for Mysql user, create a database calles app1 and it will be accessible using yourname$app1 Dont forget to set a new MYSQL PASSWORD

  1. Lets change our db connection

In your https://**yourname**.pythonanywhere.com/admin/default/edit/app1/models/db.py
You will change:
THIS db = DAL('sqlite://storage.sqlite')
IN TO THIS: db = DAL('mysql://youname:yourMYSQLPassowrd@mysql.server/yourname$app1')

  1. Click in the save icon or hit ctrl+S and then return to admin clicking edit in the main menu

  2. Now you will see the following button next to models section

  3. Click in database administration or go to https://**yourname**.pythonanywhere.com/app1/appadmin/index

  4. It is the web2py appadmin, it allows you to see the tables defined in your models and existing in your db