Development

Twitter Clone Tutorial in Web2py Part 3 – The Wall and Search Pages

Welcome to my getting started with Web2py tutorial.

This tutorial assumes very little on the part of the reader, some knowledge of a programming language will certainly help, but if not don’t worry. I’ll take you from installation through to v1 of your application.

If you don’t want to copy from the tutorial, the full source is hosted on github.

Last time we created the Models as well as the Controller and View for the home page. The next page to look at is the Wall. The Wall is specific to a given user and contains their profile details as well as their weet history.

Wall_reduced

As before we start with in the default.py Controller, add the following method to manage the behaviour for the wall.

# show user's wall, showing profile and posts
def wall():
   #Determine which user's wall is to be displayed
   user = db.auth_user(request.args(0) or me)
   #If user is invalid, return to the home page
   if not user:
       redirect(URL('home'))
   weets = db(db.weets.posted_by==user.id).select(orderby=~db.weets.posted_on,limitby=(0,100))
   return locals()

The first thing to do is determine whose wall it is that we need to display. The line responsible is worth a closer look.

user = db.auth_user(request.args(0) or me)

We have already seen db used as a means to access the database but we have not yet come across ‘request’. Request is an object available to all Controller actions and provide details of the http request that instigated the Controller action being called.

In this case we check the first (technically the zeroth) argument of the request. An argument is best described by example

http://127.0.0.1:8000/witter/default/wall/myargument/myotherargument/

In this specific instance the argument denotes the user id and so the line in question checks if a user id has been specified, if not it assumes that the requester are themselves logged in and trying to access their own wall. If neither case is true then the browser is redirected to the home page.

After that we pull out all the weets for the given user and return local variables, which are then made available to the View.

We will need a new View file called default/wall.html. As before you can create it via the application admin page.

Replace the default code with :-

{{extend 'layout.html'}}
<h2>Profile</h2>
{{=crud.read(db.auth_user,user)}}
<h2>Messages</h2>
{{for weet in weets:}}

{{=name_of(weet.posted_by)}} on {{=weet.posted_on}}:

{{=MARKMIN(weet.body)}}
{{pass}}

We’ve met crud before, it provides a nice high level abstraction to access database information, in this instance we are using it to pull the profile information about the user. The next section loops through all the weets generated in the Controller and presents them in chronological order – this very much the same logic as we saw on the home page. You can a look for yourself at http://127.0.0.1:8000/witter/default/wall or just navigate there from the Witter home page using the navigation bar at the top.

Search

For the grand finale let’s look at search.

The purpose of this page is to allow a user to search for their friends and then follow them. In some senses it is the simplest of the pages, but as we shall see it contains some interesting functionality behind the scenes.

Firstly, let’s take a look at the Controller, add the following to default.py

# a page for searching for other users
@auth.requires_login()
def search():
   form = SQLFORM.factory(Field('name',requires=IS_NOT_EMPTY()))
   if form.accepts(request):
       tokens = form.vars.name.split()
       query = reduce(lambda a,b:a&b,
                      [db.auth_user.first_name.contains(k)|db.auth_user.last_name.contains(k) 
                           for k in tokens])
       people = db(query).select(orderby=db.auth_user.first_name|db.auth_user.last_name,left=db.followers.on(db.followers.followee==db.auth_user.id))
   else:
       people = []
   return locals()

The first thing we do is create the search form, we do this like so

SQLFORM.factory(Field('name',requires=IS_NOT_EMPTY()))

The SQLFORM.factory can be used to generate a from an existing database table, or, as in this case can be used to generate a form from scratch. The form is very simple and takes a single input ‘name’.

The rest of the Controller handles the action once the input has been received  – this is what the follow is checking for:-

if form.accepts(request):

The remaining code searches for the the search terms amongst other users registered in witter and plugs them into ‘people’, it also determines which of those people the user already follows – the View is going to rely on this follower information.

The Search View

So far so good, now let’s take a look at the View, you’ll need to create a file called default/search.html under the Views section. Replace the default code with:-

{{extend 'layout.html'}}
<h2>Search for people to follow</h2>
{{=form}}

{{if people:}}
<h3>Results</h3>

{{for user in people:}}
{{=A(name_of(user.auth_user), _href=URL('wall',args=user.auth_user.id))}}
{{if user.followers.followee:}} Unfollow {{else:}} Follow {{pass}}
</div> {{pass}} {{pass}}

Much of this will now be familiar – we pull in the standard template, then the form and then iterate over the ‘people’ (if any) returned by the Controller. Where it get’s interesting is the button definitions.

Firstly we check if the searched for user is already being followed by the search, this means the button can offer to follow unfollowed witterers and unfollow the followed. So far so good, but what does this do?

<button onclick="ajax('{{=URL('follow',args=('follow',user.auth_user.id))}}',[],null);$(this).parent().html('Followed')">Follow</button>

Before I can answer that I need to explain about AJAX. AJAX stands for Asynchronous JavaScript and XML, but this name is largely misleading since it needn’t be asynchronous and needn’t use XML. In short AJAX is a technique for creating dynamic web pages, it allows pages to be updated without needing to reload the entire page, and it does this by exchanging small amounts of information with the server behind the scenes.

Strictly speaking I could have passed on using AJAX in this tutorial, but it is a really useful technique a very simple to implement.

In layman’s terms the button definition says, when the button is clicked make a poke to another Controller action called ‘follow’ and send the arguments ‘follow’ and the user id. Once this is done replace the button (which read ‘Follow’) to be some text that read ‘Followed’.

A final note about the search View is the use of the css classes ‘row’ and ‘span’. These are provided by Bootstrap which is a a popular front end frame work. Bootstrap is not part of Web2py but Web2py does support it out of the box. In this tutorial I’ve deliberately avoided focusing on the look and feel of the app, but Bootstrap provides all the tools necessary to create an attractive web application.

AJAX Callback

And that’s it for the , however, what about the follow action in the Controller? We’ve not implemented that yet. Here goes, add the following to the default.py Controller:-

# this is the Ajax callback
@auth.requires_login()
def follow():
   if request.env.request_method!='POST': raise HTTP(400)
   if request.args(0) =='follow' and not db.followers(follower=me,followee=request.args(1)):
       # insert a new friendship request
       db.followers.insert(follower=me,followee=request.args(1))
   elif request.args(0)=='unfollow':
       # delete a previous friendship request
       db(db.followers.follower==me)(db.followers.followee==request.args(1)).delete()

The most interesting line is the first one.

if request.env.request_method!='POST': raise HTTP(400)

When a browser makes a request to read a web page it uses an HTTP method called GET, this is exactly what you do everytime you point your browser at Google. There are however other HTTP methods, POST being one of them, POST is used to transmit that is expected to change the state of the application. The AJAX call that we initiate in the search,html View is a POST request.

Since this Controller action is there solely to support AJAX calls from search.html, it does not expect to handle HTTP GET requests and rejects them. You can try this for yourself by trying to View http://127.0.0.1:8000/witter/default/follow which should result in a blank page and a error message complaining of a ‘bad request’.

Search

Right so there we have it – all pages are complete, in order to try out the search and folllow/unfollow functionality you’ll need to register to extra users and have them follow each other. Be sure to notice that it is not necessary to refresh the search page every time to make a follow/unfollow request.

So in conclusion what have we achieved?

We created a new web2py application called Witter, created three pages (and an AJAX method) supporting a home page, a user wall and a user search and all backed by a database that will persist the data over time.

Are we done?

Well perhaps, I certainly am.

This tutorial has taken you from nothing at all to a functioning web2py application, that can be extended as you please.

If you are searching for ideas, you might want to move the search functionality into the home page and extend it so that search covers all weets (much like Twitter does). The UI could certainly do with a spruce up (and there are plenty of CSS tutorials out there) and as a final task you could put the thing live, gain awesome traction (due to your custom tweaks) and take over the social media world.

Feedback on this tutorial much appreciated, I’ll leave you with some useful links.

Standard

7 thoughts on “Twitter Clone Tutorial in Web2py Part 3 – The Wall and Search Pages

  1. Pingback: Twitter Clone Tutorial in Web2py Part 2 - The Home Page | Fragile

  2. m0rp# says:

    One of the best tutorial I came across for web2py. Web2py is wonderful but only if we had more tutorials like this.

    I really liked this tutorial.🙂

    Like

  3. new to web2py says:

    Absolutely agree with m0rp#. I was searching for good tutorials on web2py but didn’t found a good one. Now I could say, I found a best tutorial ever on web2py.

    You did a great job and explained it really well. Thanks.

    I would request you, if you can also write a good tutorial on unit testing a web2py application (or provide a link if you already have written), I have been searching for tutorials which explains unittesting in more detailed way (as you do) but no success yet.

    Thanks again!

    Like

  4. Roger says:

    Hi Neil,

    I landed here on part 3 via Google and had a very hard time finding part 1 of the series.
    Via the tag cloud (web2py) I could find part 2, but only via the github repo’s readme I was able to reach part 1.
    Only then I found at the very bottom of the page the links to the other parts.
    But note that the page that is shown after clicking the web2py tag in your cloud tag renders a page that has no previous/next links at the bottom.

    Maybe you might want to consider adding links on the top of the pages…
    I’ll start working through your tutorial this week-end, full with good hopes🙂

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s