Killer Web Development

by Marco Laspe

8.3 Creating Tukker Messages

Now admins can create new messages, but I doubt that this will work for our users. Only John will be admin :). This is why we need a page where users can enter messages. Ok let's start:

We can take advantage of the same mechanism web2py uses in the admin area for creating forms. Actually if we look at our sketch the message form is pretty simple, just a simple entry field for text and a button.

To show the user's messages we need to:

  1. Create a new controller-function.
  2. Create the form.
  3. Customize the form for our needs.
  4. Put the form in a new view.
  5. Style it via CSS.

First we create a new function profile in controller\default.py and create the form:

1.
2.
3.
4.
5.
def profile():
crud.messages.submit_button = "Tukker.Me"
message_form = crud.create(db.message, next="profile")

return dict(message_form=message_form)

Yes it's simple like this. We are using web2py's crud module here; CRUD is an acronym that stands for:

  • __C__reate
  • __R__ead
  • __U__pdate
  • __D__elete

Wikipedia says

CRUD - Create, read, update and delete

In computer programming, create, read, update and delete (CRUD) (Sometimes called CRUD with an "S" for Search) are the four basic functions of persistent storage. Sometimes CRUD is expanded with the words retrieve instead of read, modify instead of update, or destroy instead of delete. It is also sometimes used to describe user interface conventions that facilitate viewing, searching, and changing information; often using computer-based forms and reports. The term was likely first popularized by James Martin in his 1983 book Managing the Data-base Environment. The acronym may be extended to CRUD to cover listing of large data sets which bring additional complexity such as pagination when the data sets are to large to hold easily in memory

Go to http://localhost:8000/tukker/profile and look at the form, it's pretty much the same we have in the admin area.

To be more specific we are using the create method, which only expects us to give the database table as a parameter and then creates the form for us. The next parameter declares the function-controller that should be called, after the form is submitted. We want it to call the profile controller function again. With this 3 lines we cover implemented number 2 and 3. Now we need to customize the form that it suites our needs; the form has 4 entry fields at the moment; we only need the entry field for the message content and the submit button should say "Tukker.Me". The database fields "User Id", "Created" and "Updated" should be set automatically and are not changeable by the user.

The cool thing is, we don't have to enforce this by our self in the controller; it is enough to alter our model and set the right restrictions in models/db.py. First we want the user to only enter the message text:

1.
2.
3.
db.message.user_id.writable=False
db.message.created.writable=False
db.message.updated.writable=False

The writable property controls if a database field can be altered via the entry form, the default value is True. Compare the result at http://localhost:8000/tukker/timeline; the user is not able to enter some values to that fields anymore, but she still can read the values. To hide them we have to alter another property:

1.
2.
3.
db.message.user_id.readable=False
db.message.created.readable=False
db.message.updated.readable=False

Now the form looks much more like we want them to be. Right know it is shown via web2py's generic view - but this is just for development. We need to create a dedicated view in views/default/profile.html. Also we need to show the messages that are created by the user, otherwise our test won't pass:

1.
2.
3.
4.
5.
6.
{{extend 'layout.html'}}
<div class="six columns alpha">
{{=message_form}}
</div>
<div class="ten columns omega">
</div>

I also added a few profile Information to copy twitter's profile page. Now it's time to add a few lines of css to static/css/custom.css:

1.
2.
3.
4.
.w2p_fw {padding: 5px;}
#message_content__row .w2p_fl, #submit_record__row .w2p_fl {display: none;}
#message_content__row textarea {width: 300px;}
#submit_record__row .w2p_fw {text-align: right;}

[bild]

The form is finished. To pass our test, we need to show the messages of the current user.

Showing Tukker Messages

To show the Tukks of the current user we need to

  1. get the current user
  2. get his Tukks from the database
  3. print them in the view
  4. add some styling

To get the current user we add the following code on at the top of the profile function:

1.
2.
3.
4.
5.
6.
7.
8.
9.
def profile():
try:
nickname = request.args[0]
except:
try:
# if user is logged in
nickname = db(db.auth_user.id == auth.user.id).select()[0].nickname
except:
redirect(URL(c='default',f='index'))

This code snippet uses a try and except clause. It gives us a chance to try some code and, if it doesn't work, gives us the opportunity to act accordingly - instead of having a program error. I will describe the steps the code is doing here:

  • try to get the first element of request args, to see if we should open the profile of a certain user - we will need this later
  • except it is not there, then try to get nickname of the current user out of the database, if he is logged in (otherwise there will be no auth.user.id)
  • except we can't find an suitable entry in the database, then redirect to the homepage

Info

Why aren't we using if - else statements here?

If we would be using writing code like:

if (nickname = request.args[0])

We would get an error message, if request.args is empty. We would reference an object, that isn't there. This doesn't make much sense.

Now that we know our (current) user. We need the to get the Tukks he wrote out of the database. Therefore we add more code to the profile function:

1.
2.
3.
4.
5.
6.
7.
result = db(db.auth_user.nickname == nickname).select()[0]
user_id = result.id
messages = db( db.message.user_id==user_id).select(orderby=~db.message.updated)

...

return dict(messages=messages, message_form=message_form)

The first line gives us the user object for a certain nickname. Then we store the user's id in the variable user_id and in the next line retrieve all messages from this user. Finally we return the messages to the view.

To show the messages in the profile.html view we need a little bit more complicated construct:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
...
<
div class="ten columns omega">
<
div id="tukks">
<
h2>Tukks</h2>
{{
for message in messages:}}
<div class="tukk">
<a href="{{=URL(c="default",f="profile")}}"><img class="tukk-image" src="{{=URL('download',args=image)}}" />
<strong>{{=name}}</strong> <small>@{{=nickname}}</small></a> <small style="float:right">{{=message.updated}}</small>
<p>{{=message.content}}</p>
<div style="clear:both"></div>
</div>
{{pass}}
</div>
</div>

Because our messages object can have several items, we need to go through all messages and display every message separately. Therefore Python has the for loop - as discussed in chapter .... web2py's allow us to use for loops in our views. Because we don't have the proper intendation in our view, web2py uses {{pass}}to end a Python control structure.

Everything that is in our for loop will be printed in our page as often as the for loop iterates. In our case this means: if the user has more than 5 messages, everything that is written between {{for message in messages:}} and {{pass}} will be shown 5 times - including all HTML code.

Right the messages look somewhat unstructured. We will change this with some CSS:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
#tukks {border-bottom: 1px solid #009BC2;}

.tukk {border-top: solid 1px #009BC2;border-left: solid 1px #009BC2;border-right: solid 1px #009BC2; padding:5px}

.tukk a {text-decoration: none; color: #181818 }
.tukk a:hover {color:#009BC2}
.tukk a strong {color: #009BC2}
.tukk a:hover strong {color:#181818}

#message_content__row td {padding: 0;}
.tukk-image {width: 80px;float:left; margin-right:5px;}

Again let's see if our tests pass. If everything passes, we can commit the code to the repository :

1.
hg commit -m"User microposts"

Books often read by web2py and Python experts:

Comments

Leave a Reply

Required fields are marked *.