• The Twitter Clone – Witter

    The app will be made up of three pages

    • A home page, that displays all the weets of the user as well as those of anyone they follow

    • A wall page, that displays profile details of a given user as well as their weets

    • A search page that can be used to find others users and follow and unfollow

  • Configuraci’ion inicial:

  • En la interface de web2py crea una “Nueva aplicaci’on” / New simple application.

    commit/push

  • Register a new user

    From https://127.0.0.1:8181/witter Click ‘Login’ on the top right and then ‘Register’, enter your detail into the form and hit submit. Welcome! You are the first ever user of your application.

    nota que no tuviste que crear la p’agina de login ni de registro de usuarios, todo eso ya viene con web2py

  • a table to store posted weets

    db.define_table(‘weets’,
    Field(‘body’,’text’,requires=IS_LENGTH(140,1),label=”What’s going down?”),
    Field(‘posted_on’,’datetime’,readable=False,writable=False),
    Field(‘posted_by’,’reference auth_user’,readable=False,writable=False))

    Add to the bottom of the db.py Model file

  • #a table to store follower relationships
    db.define_table(‘followers’,
    Field(‘follower’,’reference auth_user’),
    Field(‘followee’,’reference auth_user’))

    Add to the bottom of the db.py Model file

  • #Convenience methods to make code more compact

    me = auth.user_id

    def name_of(user): return ‘%(first_name)s %(last_name)s’ % user

    Add to the bottom of the db.py Model file

  • we need to edit the menu.py Model file.

    This file configures the look of the menu and also houses meta data about the application.

    Remove the existing content and enter the following in menu.py

    response.title = “Witter”
    response.subtitle = “Super Awesome Twitter Clone in Web2py”
    response.menu = [
    (T(‘Home’), False, URL(‘default’,’home’)),
    (T(‘Wall’), False, URL(‘default’,’wall’)),
    (T(‘Search’), False, URL(‘default’,’search’)),
    ]

    This code sets the apps name and sub-title as well as defining the links on the navigation bar.

  • just to check create a method home to the default.py controller.

    def home(): return “Hello from Witter App”

    Now point your browser to http://127.0.0.1:8000/witter/default/home
    You should see a page empty but for your message.

  • Create the form with which the user con submit tweets

    def home():
    ‘’’This method allows you to send your weets and see the ones you follow’’’ #configure defaults, so they not appear on the form
    db.weets.posted_by.default = me
    db.weets.posted_on.default = request.now #create the form
    crud.settings.formstyle = ‘table2cols’
    form = crud.create(db.weets)
    return locals()

  • Force login to be able to se default/home

    *para que la p’agina de home pida permisos es muy senciollo s’olo se agrega la decoracion @auth arriba del m’etodo que queremos sea restringido.

    @auth.requires_login()
    def home():
    ‘’’First point of call, contains all weets and all weets of those you follow’’’ #configure defaults, so they not appear on the form
    db.weets.posted_by.default = me
    db.weets.posted_on.default = request.now #create form with which the user can submit weets
    crud.settings.formstyle = ‘table2cols’
    form = crud.create(db.weets)
    return locals()

  • In the controller replace the index method with:

    def index():
    if auth.user: redirect(URL(‘home’))
    return locals()

    This method simply checks if you are logged in and if so redirects to the ‘home’ page. If not you are presented with the default landing page that you saw when you first started up Web2py.

  • #In Views section and create a new file called default/home.html

    The path of the file denotes that it is the View of the default/home Controller.

    Replace the default code with the following:

    {{extend ‘layout.html’}}
    {{=form}}

    <script>$(‘textarea’).css(‘width’,’600px’).css(‘height’,’50px’);</script>

  • Now lets get all the weets of the current user and all his friends.

    *In the controller default.py method home at the end append this code:

    form = crud.create(db.weets) #determine who the user follows
    my_followees = db(db.followers.follower==me)
    me_and_my_followees = [me]+[row.followee for row in my_followees.select(db.followers.followee)] #Pull all weets to be displayed
    weets = db(db.weets.posted_by.belongs(me_and_my_followees)).select(orderby=~db.weets.posted_on,limitby=(0,100))
    return locals()

  • in views/deafult/home.html display all the weets

    add this for loop to the end of file:

    {{for weet in weets:}}

    <div style="background: #f0f0f0; margin-bottom: 5px; padding: 8px;">

    <h3>{{=name_of(weet.posted_by)}} on {{=weet.posted_on}}:</h3>
    {{=MARKMIN(weet.body)}}
    </div>
    {{pass}}

    If you visit 127.0.0.1:8181/witter/default/home you should see

    something like this:

  • Show user’s wall, showing profile and posts

    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.

    In controller write this method

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

  • Create the view for the wall

    {{extend ‘layout.html’}}

    <h2>Profile</h2>
    {{=crud.read(db.auth_user,user)}}

    <h2>Messages</h2>
    {{for weet in weets:}}

    <div style="background: #f0f0f0; margin-bottom: 5px; padding: 8px;">

    <h3>{{=name_of(weet.posted_by)}} on {{=weet.posted_on}}:</h3>
    {{=MARKMIN(weet.body)}}
    </div>
    {{pass}}

    the page will look like this:

  • a page for searching for other users

    The purpose of this page is to allow a user to search for their friends and then follow them.

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

    @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 Search View
    Create a file called default/search.html

    {{extend ‘layout.html’}}

    <h2>Search for people to follow</h2>
    {{=form}}

    {{if people:}}

    <h3>Results</h3>

    {{for user in people:}}

    <div class="row">

    <div class="span3 offset1">{{=A(name_of(user.auth_user), _href=URL(‘wall’,args=user.auth_user.id))}}</div>

    <div class="span1">
    {{if user.followers.followee:}}

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

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

    This view should look like this:

  • #Implement the follow action in the 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()
{"cards":[{"_id":"4db7fb635a465dd5e5000038","treeId":"4db7f4e25a465dd5e5000035","seq":1332317,"position":1,"parentId":null,"content":"# The Twitter Clone – Witter\n\n\nThe app will be made up of three pages\n\n* A home page, that displays all the weets of the user as well as those of anyone they follow\n\n* A wall page, that displays profile details of a given user as well as their weets\n\n* A search page that can be used to find others users and follow and unfollow"},{"_id":"4db7ff665a465dd5e5000039","treeId":"4db7f4e25a465dd5e5000035","seq":1332325,"position":2,"parentId":null,"content":"Configuraci'ion inicial:\n[ ] Haz un fork del repo agus/web2py para tu equipo.\n\n[ ] renombra el repo como witter\n\n[ ] agrega a tus companieros como laboradores.\n\n[ ] que todos hagan un clon del repo inicial\n\n[ ] probar que todos puedan ejecutar el servidor de web2py de manera local."},{"_id":"4db800e65a465dd5e500003a","treeId":"4db7f4e25a465dd5e5000035","seq":1332329,"position":3,"parentId":null,"content":"En la interface de web2py crea una \"Nueva aplicaci'on\" / New simple application.\n\ncommit/push"},{"_id":"4db8104a5a465dd5e500003b","treeId":"4db7f4e25a465dd5e5000035","seq":1335863,"position":4,"parentId":null,"content":"# Register a new user\n\nFrom https://127.0.0.1:8181/witter Click ‘Login’ on the top right and then ‘Register’, enter your detail into the form and hit submit. Welcome! You are the first ever user of your application.\n\n*nota que no tuviste que crear la p'agina de login ni de registro de usuarios, todo eso ya viene con web2py*"},{"_id":"4db815315a465dd5e500003c","treeId":"4db7f4e25a465dd5e5000035","seq":1332344,"position":5,"parentId":null,"content":"# a table to store posted weets\ndb.define_table('weets',\n Field('body','text',requires=IS_LENGTH(140,1),label=\"What's going down?\"),\n Field('posted_on','datetime',readable=False,writable=False),\n Field('posted_by','reference auth_user',readable=False,writable=False))\n\n*Add to the bottom of the db.py Model file*\n"},{"_id":"4db817c15a465dd5e500003d","treeId":"4db7f4e25a465dd5e5000035","seq":1332343,"position":6,"parentId":null,"content":"#a table to store follower relationships\ndb.define_table('followers',\n Field('follower','reference auth_user'),\n Field('followee','reference auth_user'))\n\n*Add to the bottom of the db.py Model file*"},{"_id":"4db81b6f5a465dd5e500003e","treeId":"4db7f4e25a465dd5e5000035","seq":1332350,"position":7,"parentId":null,"content":"#Convenience methods to make code more compact\n\n> me = auth.user_id\n> \n> def name_of(user): return '%(first_name)s %(last_name)s' % user\n\n*Add to the bottom of the db.py Model file*"},{"_id":"4db81f305a465dd5e500003f","treeId":"4db7f4e25a465dd5e5000035","seq":1332353,"position":8,"parentId":null,"content":"# we need to edit the menu.py Model file. \n\nThis file configures the look of the menu and also houses meta data about the application.\n\nRemove the existing content and enter the following in menu.py\n\n> response.title = \"Witter\"\nresponse.subtitle = \"Super Awesome Twitter Clone in Web2py\"\nresponse.menu = [\n(T('Home'), False, URL('default','home')),\n(T('Wall'), False, URL('default','wall')),\n(T('Search'), False, URL('default','search')),\n]\n\n*This code sets the apps name and sub-title as well as defining the links on the navigation bar.*\n"},{"_id":"4db831c65a465dd5e5000040","treeId":"4db7f4e25a465dd5e5000035","seq":1332362,"position":9,"parentId":null,"content":"# just to check create a method home to the default.py controller.\n\n> def home(): return \"Hello from Witter App\"\n\n*Now point your browser to http://127.0.0.1:8000/witter/default/home\nYou should see a page empty but for your message.*"},{"_id":"4db83e3e5a465dd5e5000041","treeId":"4db7f4e25a465dd5e5000035","seq":1332368,"position":10,"parentId":null,"content":"# Create the form with which the user con submit tweets\n\n> def home():\n '''This method allows you to send your weets and see the ones you follow'''\n #configure defaults, so they not appear on the form\n db.weets.posted_by.default = me\n db.weets.posted_on.default = request.now\n #create the form\n crud.settings.formstyle = 'table2cols'\n form = crud.create(db.weets)\n return locals()"},{"_id":"4db854cb5a465dd5e5000045","treeId":"4db7f4e25a465dd5e5000035","seq":1332412,"position":10.5,"parentId":null,"content":"# Force login to be able to se default/home\n\n*para que la p'agina de home pida permisos es muy senciollo s'olo se agrega la decoracion @auth arriba del m'etodo que queremos sea restringido.\n\n> @auth.requires_login()\ndef home():\n '''First point of call, contains all weets and all weets of those you follow'''\n #configure defaults, so they not appear on the form\n db.weets.posted_by.default = me\n db.weets.posted_on.default = request.now\n #create form with which the user can submit weets\n crud.settings.formstyle = 'table2cols'\n form = crud.create(db.weets)\n return locals()"},{"_id":"4db8452b5a465dd5e5000042","treeId":"4db7f4e25a465dd5e5000035","seq":1332370,"position":11,"parentId":null,"content":"# In the controller replace the index method with:\n\n> def index():\n if auth.user: redirect(URL('home'))\n return locals()\n\n*This method simply checks if you are logged in and if so redirects to the ‘home’ page. If not you are presented with the default landing page that you saw when you first started up Web2py.*"},{"_id":"4db848d95a465dd5e5000043","treeId":"4db7f4e25a465dd5e5000035","seq":1332395,"position":12,"parentId":null,"content":"#In Views section and create a new file called default/home.html \n\n*The path of the file denotes that it is the View of the default/home Controller.*\n\n*Replace the default code with the following:*\n\n>{{extend 'layout.html'}}\n{{=form}}\n<script>$('textarea').css('width','600px').css('height','50px');</script>\n\n[ ] when you visit 127.0.0.1:8000/witter/default/home you should see a form\n\n"},{"_id":"4db860575a465dd5e5000046","treeId":"4db7f4e25a465dd5e5000035","seq":1332422,"position":12.5,"parentId":null,"content":"# Now lets get all the weets of the current user and all his friends.\n\n*In the controller default.py method home at the end append this code:\n\n>form = crud.create(db.weets)\n #determine who the user follows\n my_followees = db(db.followers.follower==me)\n me_and_my_followees = [me]+[row.followee for row in my_followees.select(db.followers.followee)]\n #Pull all weets to be displayed\n weets = db(db.weets.posted_by.belongs(me_and_my_followees)).select(orderby=~db.weets.posted_on,limitby=(0,100))\n return locals()"},{"_id":"4db84ddc5a465dd5e5000044","treeId":"4db7f4e25a465dd5e5000035","seq":1332428,"position":13,"parentId":null,"content":"# in views/deafult/home.html display all the weets\n\n*add this for loop to the end of file:*\n\n>{{for weet in weets:}}\n<div style=\"background: #f0f0f0; margin-bottom: 5px; padding: 8px;\">\n<h3>{{=name_of(weet.posted_by)}} on {{=weet.posted_on}}:</h3>\n{{=MARKMIN(weet.body)}}\n</div>\n{{pass}}\n\n If you visit 127.0.0.1:8181/witter/default/home you should see\n[ ] A form to post tweets\n[ ] A list of all your weets.\n\nsomething like this:\n![](http://fragile.org.uk/wp-content/uploads/2013/06/Home_reduced.png)"},{"_id":"4db878255a465dd5e5000047","treeId":"4db7f4e25a465dd5e5000035","seq":1332436,"position":14,"parentId":null,"content":"# Show user's wall, showing profile and posts\n\n*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.*\n\n![](http://fragile.org.uk/wp-content/uploads/2013/06/Wall_reduced.png)\n\n*In controller write this method*\n\n>def wall():\n #Determine which user's wall is to be displayed\n user = db.auth_user(request.args(0) or me)\n #If user is invalid, return to the home page\n if not user:\n redirect(URL('home'))\n weets = db(db.weets.posted_by==user.id).select(orderby=~db.weets.posted_on,limitby=(0,100))\n return locals()"},{"_id":"4db87d4c5a465dd5e5000048","treeId":"4db7f4e25a465dd5e5000035","seq":1332448,"position":15,"parentId":null,"content":"# Create the view for the wall\n\n>{{extend 'layout.html'}}\n<h2>Profile</h2>\n{{=crud.read(db.auth_user,user)}}\n<h2>Messages</h2>\n{{for weet in weets:}}\n<div style=\"background: #f0f0f0; margin-bottom: 5px; padding: 8px;\">\n<h3>{{=name_of(weet.posted_by)}} on {{=weet.posted_on}}:</h3>\n{{=MARKMIN(weet.body)}}\n</div>\n{{pass}}\n\n*the page will look like this:*\n![](http://fragile.org.uk/wp-content/uploads/2013/06/Wall_reduced.png)"},{"_id":"4db88ec85a465dd5e5000049","treeId":"4db7f4e25a465dd5e5000035","seq":1332459,"position":16,"parentId":null,"content":"# a page for searching for other users\n\n*The purpose of this page is to allow a user to search for their friends and then follow them.*\n\n*Firstly, let’s take a look at the Controller, add the following to default.py*\n\n>@auth.requires_login()\ndef search():\n form = SQLFORM.factory(Field('name',requires=IS_NOT_EMPTY()))\n if form.accepts(request):\n tokens = form.vars.name.split()\n query = reduce(lambda a,b:a&b,\n [db.auth_user.first_name.contains(k)|db.auth_user.last_name.contains(k) \\\n for k in tokens])\n 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))\n else:\n people = []\n return locals()"},{"_id":"4db8978c5a465dd5e500004a","treeId":"4db7f4e25a465dd5e5000035","seq":1332474,"position":17,"parentId":null,"content":"#The Search View\n*Create a file called default/search.html*\n\n{{extend 'layout.html'}}\n<h2>Search for people to follow</h2>\n{{=form}}\n\n{{if people:}}\n<h3>Results</h3>\n\n{{for user in people:}}\n<div class=\"row\">\n<div class=\"span3 offset1\">{{=A(name_of(user.auth_user), _href=URL('wall',args=user.auth_user.id))}}</div>\n<div class=\"span1\">\n{{if user.followers.followee:}}\n<button onclick=\"ajax('{{=URL('follow',args=('unfollow',user.auth_user.id))}}',[],null);$(this).parent().html('Unfollowed')\">Unfollow</button>\n{{else:}}\n<button onclick=\"ajax('{{=URL('follow',args=('follow',user.auth_user.id))}}',[],null);$(this).parent().html('Followed')\">Follow</button>\n{{pass}}\n</div>\n</div>\n{{pass}}\n{{pass}}\n\n\nThis view should look like this:\n![](http://fragile.org.uk/wp-content/uploads/2013/06/Search_reduced.png)"},{"_id":"4db8994e5a465dd5e500004b","treeId":"4db7f4e25a465dd5e5000035","seq":1332471,"position":18,"parentId":null,"content":"#Implement the follow action in the Controller\n\n\n># this is the Ajax callback\n@auth.requires_login()\ndef follow():\n if request.env.request_method!='POST': raise HTTP(400)\n if request.args(0) =='follow' and not db.followers(follower=me,followee=request.args(1)):\n # insert a new friendship request\n db.followers.insert(follower=me,followee=request.args(1))\n elif request.args(0)=='unfollow':\n # delete a previous friendship request\n db(db.followers.follower==me)(db.followers.followee==request.args(1)).delete()"}],"tree":{"_id":"4db7f4e25a465dd5e5000035","name":"Clon de twitter","publicUrl":"witter"}}