Making a Flickr Killer With TurboGears – Part 2: A Flickr Clone in 37 Minutes Flat

This is the second installment of the lecture I gave at the Israeli Pythoneers Meeting. In case that you missed it, it is recommended that you read the first part of it.

At this point, I closed OpenOffice Impress and said that I would demonstrate how quickly you could get a functional Web application up and running with africacasinosa TurboGears.

Setting Things Up

I’ve quick-started a new project called TurboGallery…

$ tg-admin quickstart
Enter project name: TurboGallery
Enter package name [turbogallery]: <Enter>
Do you need Identity (usernames/passwords) in this project? [no] <Enter>

…and gone inside its directory to run it:

$ cd TurboGallery
$ python

I started Firefox and browsed to http://localhost:8080. “There is already something to see on the site,” I said. The TurboGears’ default welcome page was showing up.

“The next thing I always do is to delete the entire body of the welcome template.” So, I opened welcome.kid in my favorite text editor and did just that. I then refreshed the page in Firefox. All the contents were gone except for the TurboGears’ header and the footer, which said, “TurboGears under the hood.” Someone asked if there was any way to remove those two remaining items. “Absolutely,” I said. It was time to mention master.kid. I opened the file and explained that it is used to render the layout that is common to all pages. As such, you do not have to duplicate it in all your templates. I next removed the TurboGears’ footer but left the header in its place.

I then opened and added a class that would represent a single photo:

from datetime import datetime

class Photo(SQLObject):
    title = UnicodeCol()
    date = DateTimeCol(
    image = BLOBCol()
    thumbnail = BLOBCol()

A photo will have a title and a date field, which indicates the time it was uploaded. The field’s default argument makes the date of a newly created photo object be the current date. That way, you don’t need to specify this information every time you create a new photo. In the above definition, we don’t give the date field the current time. Rather, we give it a function that returns the current time. Whenever a photo is created, this function will be called to determine the value for this field. We store the images inside the database together with a thumbnail. BLOB stands for binary large object. The thumbnail part was not actually used in the tutorial.

Next, I created a database with

$ tg-admin sql create

I was then asked how TurboGears knows where and how to access the database. I replied that the default TurboGears’ setting uses SQLite, which creates a database-in-a-file. It is very convenient to have your database readily available as a normal file while your project is in its development and testing phases.

To save time, I have prepared in advance a database that contains four photos. I replaced the database file that was just created with my copy. The next thing to do will be to display the photos on the main page. I modified the index() method into…

def index(self):
    photos =
    return dict(photos=photos)

…and uncommented the “from model import *” line. The index() method is called whenever the front page of the Web site is viewed. I explained the expose() decorator, which makes a method available to the outside world. Without it, the method could not be accessed from the Web. The template argument specifies which template should be used to render the page.

The index() method gets a list of all photos from the database and returns it inside a dictionary. TurboGears passes this dictionary to the template. I then moved back to welcome.kid and added the following to the body:

     <li py:for="photo in photos">${photo.title}</li>

I refreshed the page, and the list of photos was displayed. It is educational to view the source of the resulting page, but the people who were listening to my lecture wanted to see the photos straightaway.

Seeing Some Photos

To see the photos, we’ll have to add an IMG element to the list. The IMG element will get the photo file from another URL. I’ve changed the welcome.kid list into…

<ul class="photo_list">
    <li py:for="photo in photos">
        <img src="/images/${}"
              id="image${}" width="160"/><br/>
        <a href="/photo_info/${}">${photo.title}</a>

…and then added the following photo_info() method to the controller:

def images(self, photo_id):
    return "Hello "+photo_id

I next browsed to /images/world, and the browser displayed “Hello world.” The goal of this step was to demonstrate to the audience how TurboGears (CherryPy) maps URLs to methods. It was also intended to illustrate how easily a part of the URL can be transformed into positional arguments.
The right thing to put in this function would be a query that would obtain the photo from the database and return the associated jpeg file. Here is the code:

    def images(self, photo_id):
        photo = Photo.get(photo_id)
        return photo.image

I refreshed the page, and the photos I’d prepared were now showing. (I also changed the CSS file when no one was looking to have this layout.)
Evangalizing Firefox in Thailand
In the second picture, you can see me evangelizing for the use of Firefox in Thailand.
Since this is just an example application to make things simple, the image is sent in full size and is resized by the browser.
Clicking on the caption below the images sends us to a page we haven’t yet created. This page will display the photo in full size together with some statistics on it. I saved welcome.kid as photo_info.kid and changed the body contents to:

    <h1>Photo Details for "${photo.title}"

    <img id="image${}" src="/images/${}"/>

        <li>Image size: ${len(photo.image)} bytes</li>
        <li>Uploaded at: ${}</li>

The corresponding method in would be:

    def photo_info(self, photo_id):
        photo = Photo.get(photo_id)
        return dict(photo=photo)

Share Your Photos

An online gallery application is quite useless if its users can’t upload photos from their own computers. As such, we need to create an image upload form. In TurboGears, creating forms and validating user input is very easy. I’ve added a form definition to the top of The first part defines the fields:

from turbogears.widgets import *
from turbogears import *

class AddPhotoFields(WidgetsList):
    title = TextField(label='Title:')
    image = FileField(label='Photo:')

The form will have a text input field for the title of the photo as well as a field that enables the selection of an image file from the user’s computer. The second part of the definition is the validation schema for this form:

class AddPhotoSchema(validators.Schema):
    title = validators.String(not_empty=True, max=16)

“The touchiest issue with any Web application is its users,” I said. “Without them, there is no need to worry about bugs or invalid input.” The validation makes sure the input your Web application receives is a sound one. In many cases, further validation is needed. The above validator makes sure the title is not empty and is no longer than 16 letters. One audience member asked if the validation can be specified inside the fields definition. Yes, it is possible to specify a validator as a keyword argument to a field definition, but making the validation schema exterior to the fields definition renders it possible to define a more complex schema that involves field dependency or logical operators. A common instance where such a schema is useful is in the validation of a registration form, where you have to check whether or not the entered password field text and the “reenter password field” match.

The last part of the form definition ties the previous two parts together, with text for the submit button and a URL that will handle the form data:

add_photo_form = TableForm(fields=AddPhotoFields(),

I’ve created a template named add.kid that will display just the form:

   <h1>Add New Photo</h1>

I’ve also added a controller method to make this page accessible…

    def add(self):
        return dict(form=add_photo_form)

…and I’ve linked to it from welcome.kid.

Here is a screenshot of this page:
Add photo form
As you can see, TurboGears gives us a nice form without us having to type in any HTML at all. Clicking the Upload! Button posts the data to the /upload URL. To test the form, I’ve added the corresponding method to the controller:

    def upload(self, title, image):
        return "hi"

The decorators that are attached to this method make TurboGears validate its input using the add_photo_form. If a validation error occurs, the response is handled by the add() method, which just displays the form again along with the validation errors. So if, for example, we type in a too-long title, we will get the following:
TurboGears form validation errors
I would now really like to make the method save the image in a new photo object. The following code will do:

    def upload(self, title, image):
        image =
        Photo(title=title, image=image, thumbnail=None)
        flash("Image successfully added!")
        raise redirect('/')

Yes, handling a file upload in TurboGears is just a matter of reading from a file-like object. It is that simple. The next line creates a photo object with the title and the image data. The flash() method makes the given message appear on the next page, and we redirect to the main page. I filled out the form to upload an image that was downloaded from my camera. Here’s what I got:
Rotate photos in TurboGears
Damn! I hate it when my camera decides to rotate an image that has already been uploaded. So, let’s add an AJAX image-rotation tool. A click on the rotate link will rotate the image in place without requiring the entire page to be reloaded. We first add a method to rotate the image to our controller:

    def rotate(self, photo_id):
        import Image
        from cStringIO import StringIO
        photo = Photo.get(photo_id)
        image =
        rotated = image.rotate(90)
        photo.image = rotated.tostring('jpeg', 'RGB')
        return dict(photo_id=photo_id, size=rotated.size)

The method uses PIL – Python Imaging Library. It loads the image from the database, rotates it, and then stores it again in the database. The method returns a dictionary containing the photo_id and the new photo dimensions. It is set to return the data in JSON format, which makes it extremely easy to use in Javascript. I directly entered http://localhost:8080/rotate/2 to show what the JSON object looks like. I then refreshed the main page to verify that the photo had been rotated. Next, I rotated it three more times until it was straight again.

I then went back to welcome.kid and added a rotate link for each photo:

<ul class="photo_list">
    <li py:for="photo in photos">
        <img src="/images/${}"
              id="image${}" width="160"/><br/>
        <a href="/photo_info/${}">${photo.title}</a>
        <a href="#" onclick="rotate_photo(${}); return false;">(rotate)</a>

Clicking on the “rotate” text located next to a photo will call a Javascript function that receives the corresponding photo ID. I’ve added the implementation of rotate_photo() to the top of the welcome.kid template. It uses MochiKit, which must be enabled in config/app.cfg (it is explained how to do so in that file).

The function makes an asynchronous call to the rotate URL, providing it with the photo ID. Once the response arrives, update() is called and is provided with the JSON object we returned from the controller’s rotate() method. In Javascript, you can use dot notation to access the keys in that dictionary. When update() is called, the image has already been rotated at the server. Therefore it is the right time for the browser to fetch it again. To perform this re-fetching, update() sets the image src attribute to the image URL, but in order to prevent the browser from displaying the cached old image, we add a random argument to the URL. You can see a related post describing how to prevent browsers from caching. To make the image() controller method accept this argument and ignore it, its definition becomes:

def images(self, photo_id, random=None):

I then refreshed the main page and rotated the photos a few times to demonstrate how slick this functionality is (especially when you’re working on the server). You can see it in the video below:

Next, I uploaded another image I had prepared in advance, this one called questions.jpg, using the upload form. Here it is:

It says “Questions?” in Hebrew, signifying that the lecture was over and it was time to ask questions.

Download the full source code of TurboGallery.

This entry was posted in python, turbogears. Bookmark the permalink.

199 Responses to Making a Flickr Killer With TurboGears – Part 2: A Flickr Clone in 37 Minutes Flat

  1. The other day, while I was at work, my cousin stole my iphone and tested to see if it
    can survive a 40 foot drop, just so she can be a youtube sensation. My apple ipad is now destroyed and she has 83 views.
    I know this is entirely off topic but I had to share it with someone!

  2. Hey I know this is off topic but I was wondering if you knew of any widgets I could
    add to my blog that automatically tweet my newest twitter updates.

    I’ve been looking for a plug-in like this for quite some time and was hoping
    maybe you would have some experience with something like this.
    Please let me know if you run into anything. I truly enjoy reading your blog and I look forward to your new updates.

  3. Excellent blog here! Also your site loads up very fast!

    What web host are you using? Can I get your affiliate link
    to your host? I wish my website loaded up as fast as yours lol

  4. Nice post. I learn something new and challenging on websites I stumbleupon every day.
    It’s always helpful to read content from other writers and practice
    a little something from other sites.

  5. Excellent, what a web site it is! This blog gives useful information to us, keep it up.

  6. Hey! I’m at work browsing your blog from my new apple iphone!

    Just wanted to say I love reading through your blog and look forward to all your posts!
    Carry on the superb work!

  7. Hi there it’s me, I am also visiting this website regularly, this
    website is actually nice and the users are truly sharing good thoughts.

  8. When someone writes an piece of writing he/she keeps the
    thought of a user in his/her brain that how a user can understand it.
    Therefore that’s why this post is amazing. Thanks!

  9. Fantastic site you have here but I was wondering if you knew of any user discussion forums that cover the same topics discussed in this article?
    I’d really like to be a part of community where
    I can get advice from other experienced people that share the same interest.
    If you have any recommendations, please let me know. Kudos!

    Here is my webpage: robux generator

  10. Admiring the hard work you put into your blog and in depth information you offer.
    It’s good to come across a blog every once in a while that isn’t the same out of date rehashed information. Excellent read!
    I’ve saved your site and I’m adding your RSS feeds to
    my Google account.

  11. I believe this is among the so much significant information for
    me. And i am satisfied studying your article. However want to observation on few common things, The website taste is great, the articles is in point of fact excellent :
    D. Excellent job, cheers

  12. dish mexico says:

    It’s awesome to visit this web page and reading the views of all
    mates on the topic of this piece of writing, while I am also zealous of getting familiarity.

  13. Hey there! This is kind of off topic but I need some advice from an established blog.
    Is it hard to set up your own blog? I’m not very techincal but
    I can figure things out pretty fast. I’m thinking about making my own but I’m not sure where to start.
    Do you have any tips or suggestions? Cheers

  14. What’s up colleagues, how is everything, and what you wish for to say about this post, in my view its actually remarkable in favor
    of me.

    Also visit my site; Daine E. Preedom

  15. Kyllikki says:

    If you want to obtain a great deal from this piece of writing then you have to apply these methods
    to your won blog.

    Klikkaa: [Kyllikki]

  16. Hey I know this is off topic but I was wondering if you knew of any widgets I
    could add to my blog that automatically tweet my newest twitter
    updates. I’ve been looking for a plug-in like this for quite some time and was hoping maybe you
    would have some experience with something like this.

    Please let me know if you run into anything. I truly enjoy reading your blog
    and I look forward to your new updates.

    My site :: Fifa 15 Online Hack

  17. Toni says:

    Hi, yeah this paragraph is really fastidious and I have learned lot of things from it regarding blogging.

    Visit my site Call Of Duty Advanced Warfare Hack, Toni,

  18. top casinos says:

    We are a bunch of volunteers and opening a new scheme in our community.
    Your site offered us with helpful info to work on. You have done a formidable
    activity and our whole neighborhood will likely be thankful
    to you.

  19. baby names says:

    What a information of un-ambiguity and preserveness of precious familiarity regarding unexpected feelings.

  20. Terhikki says:

    Hurrah, that’s what I was seeking for, what a information! present here at this web site, thanks admin of this website.


  21. part says:

    When they disappear in another piece of clothing,
    especially a thing that doesn’t get worn that always, it may be months prior to deciding to discover the missing sock.
    You are going to work with bolts to connect the camera mount and operator handle to the boards.

    I highly suggest the Whirlpool Cabrio Dryer for anyone who is within the
    market with an amazing dryer – and if you are looking for any washer,
    the Cabrio Washer can be just as astonishing. This minimizes mold
    and mildew that may cause the washer to come with an odor that can be transferred for your clothes.

    You can determine your article’s readability using the Flesch-Kincaid Grade Level scale which assigns a grade level towards
    the written material. t know the sort of information you need to contain in your resume.
    part sears outlet The
    organic results will demonstrate ‘basic’, ‘intermediate’, or ‘advanced’ reading levels for each
    from the page results. Administrative are for those that
    are inside a job but need a great position or appraisal or probably
    moving away from employment for another one.

  22. Many thanks for the good article, I was hunting for specifics such as this, visiting take a look at the various other articles.

  23. Fred says:

    Very good post. I’m going through many of these
    issues as well..

  24. Excellent site. A lot of useful information here. I am sending it to some pals ans additionally sharing in delicious. And obviously, thank you for your effort!

  25. Ensure that your resume makes it easy for the readers to identify your capabilities and feel your enthusiasm to
    be a part of their workforce. There are many different ways to enjoy the wonder of a Disney holiday.
    A form of the herb licorice, called deglycyrrhizinated licorice (DGL),
    was explored for canker sores in a small study.

  26. Milan says:

    Ensure that your resume makes it easy for the readers to identify your capabilities and
    feel your enthusiasm to be a part of their
    workforce. To take hold of this gentle wisdom and to remember that if I
    let go and be held by Mother Nature’s ebb and flow that I too will be brought back to pools of stillness.
    As its name implies, the boat itself, created by an act of
    Congress in 1916, is the country’s only full-fledged floating post office with the power to cancel mail.

  27. The types that are available are numerous and you can choose your pick from the ones that range
    from hand press models to ones that are electrically operated.
    The temple shines with infinite reflections from hundreds of crystallized glass pieces.
    The number of expected wedding guests must also be kept in mind including the proportion between relatives and friends.

  28. Deloras says:

    Inside your private Caribbean beach villa,
    you can kick up your heels and relax in the plush surroundings.
    A good majority move down from the cold up north to our beautiful,
    white sandy beaches. A form of the herb licorice, called deglycyrrhizinated
    licorice (DGL), was explored for canker sores in a small study.

  29. Customers might look at the fees and services of lenders classic ugg bomber boots online business – the newest explosion inside the business world is around the internet.

  30. Wilburn says:

    London dos mil doce Join In : es un programa oficial para dispositivo móvil con sistema operativo iOS – Android y/ Blackberry.

  31. Good way of describing, and pleasant piece of writing to take facts regarding my
    presentation subject matter, which i am going to present in school.

  32. Rena says:

    It’s very effortless too find out any topic on web as compared to textbooks, as I found
    this post at this site.

  33. Carmelo says:

    I must thank you for the efforts you’ve put in writing thuis website.
    I really hope to check out the same high-grade content by you later on as well.
    In truth, youjr creative writing abilities has inspired me to get myy own website now 😉

  34. Isis says:

    Awesome website you have here but I was wanting to know if you knew of any forums
    that cover the same topics talked about in this article? I’d really
    like to be a part of community where I can get comments from other experienced individuals
    that share the same interest. If you have any recommendations, please let me
    know. Bless you!

  35. I believe this is one of the so much vital
    info for me. And i’m glad studying your article.

    However wanna remark on few common things, The web site taste is ideal, the articles is
    actually grerat : D. Just right activity, cheers

  36. wonderful issues altogether, you just won a new reader.
    What would you suggest in regards to your put up that you simply made some days
    ago? Any positive?

  37. Echt tolle Seite. Rubbish bin eigentlich nur per Zufall hier gelandet, aber ich bin jetzt schon complete von der tremendous Seite beeindruckt. Gratuliere dazu!! Viel Erfolg noch durch der sehr guten Home-page mein Freund.

  38. Jo-Ann Fabrics is internet hosting a Totally free Make It-Take It for kids
    this Saturday, August 20, 2011 from 11am to 1pm
    at stores nationwide. Kids can Xyron Back again-to-School venture.

    Just about each staff member I encountered was an American, with the exception of the housekeeping staff.

    They all wore nametags that outlined their American hometown.

    REMEMBER: A person’s name is the sweetest audio they will listen to in any language.
    When you use their names, you will make them feel appreciated,
    welcome and essential. Perhaps it’s printed on a plastic clip.
    Perhaps it’s created on a paper nametag.
    Even if it hangs from a lanyard, stares you in the encounter,
    appear at it, and SAY IT! Step on to their front porch,
    and WOW them!

    If you are leasing area then you can of program skip this step but
    if you are having the party in your house then it is time to clean and child proof your house.
    You may want to use streamers like “police tape” reducing off locations of your house that kids may discover their way into.
    Also you will want to place absent any breakables that
    could get ruined by excited, enjoyable looking for kids.
    May be you could use the streamers as a runway displaying the path toward the star of the party- the birthday child.

    The Airport Safety place a woman on our case. I went to the Relaxation Room
    while the intercom was stating ‘All unattended baggage will be turned over to the Chicago Law enforcement Dept.’ I believed we’d by
    no means see that situation once more. Nevertheless, inside about 40 minutes,
    the lady arrived with our bag! We were so overjoyed we each
    gave her a large hug and thanked her. We were happy
    we still experienced an hour prior to catching our subsequent plane

    Hand Sanitizer – this is a given. It’s not usually convenient to run to a restroom to wash your hands prior to snacking.
    So carry a little bottle of hand sanitizer and unwind.

    Go out of your way to use the names of ten unsuspecting nametaggers this 7 days.
    Keep a journal, e-mail the encounters it to me and I’ll publish your adventures on my blog!

  39. Risa says:

    Higgledy-piggledy is dead right for Indian hill-stations in general and Darjeeling in pcariaultr. I’ve just added some more photos, from the time the skies cleared (temporarily) and from yesterday’s Toy Train ride down to Kurseong (30 km, 3 hours) and back on the narrow-gauge steam-powered train: lots of fun!

  40. Wah bagus ya postingannya gan, terima kasih postingannya sangat informatif, berguna sekali gan terima kasih,
    bagus banget , bagus gan, boleh dong saya share gan, mau saya kirimgan , boleh ya saya share buat
    kolega saya

  41. Hi there! Someone iin my Facebook group shared this site with
    us so I came to take a look. I’m definitely enjoying
    the information. I’m bookmarking and will bbe tweeting this to my followers!
    Wonderful blog and brilliant style and design.

  42. Et’ѕ stɑte yoᥙ’re ɑ newcomer foг the wοrld
    of revenue ɑnd so агe seeking to earn money online,
    bbut ʏoᥙ’rᥱ starting withh ɑ tiny startup budget.

  43. The compounding of these pimple treatment ingredients has the capability to minimize
    oil generated by the skin. Don’t worry about them;
    just talk to your pharmacist or doctor if you are not already using
    some creams. The blood cells work by flattening the damaged area.

  44. You can get a smooth skin by acquiring the right information and with proper guidance you would
    be amazed at the texture of your skin. Mix it until the mixture foams and turns
    into a paste like consistency. Anti-oxidant chemicals are valued for their wide
    healing actions.

  45. -Buy a NUTRITION ALMANAC and become familiar with what foods contain which elements.
    Also, it has been proven that some specific cuts of these animals provide less cholesterol than meat, the leaner
    pieces provide between 55 and 75 milligrams per 100 grams,
    an amount lower unquestionably lamb meat or vaccines. Whole grains and nuts are a vital food source and boost your energy immediately.

  46. says:

    Excellent weblog here! Additionally your website a lot up very fast!
    What host are you using? Can I am getting your
    associate link for your host? I want my website loaded up as fast as yours lol

  47. We stumbled over here by a different web page and thought
    I might check things out. I like what I see so now
    i am following you. Look forward to looking over your web page again.

  48. You have to appreciate that you are not finding a complete exercise
    like this, although these are okay every once in a little while.
    You may truly be creating some muscular imbalances which could result in accidents.
    Both most common exercises in which people cheat are standing waves (dumbbell or barbell)
    as well as the benchpress. People often lean backward, giving extra power with that they
    could transfer the fat to themselves when performing curls.
    This is a good way to hurt your back or just not operate your arms in any way.
    You shouldn’t be moving your back; in-fact, only your forearms should be shifting.

    With the benchpress, I realize that since they must much fat around the
    bar a great deal of folks tend to simply do half or quarter distributors.
    Many raise up their thighs off a floor, will also arch their back, or jump the
    fat off their chest. These all are terrible, specially
    the center one, as it can cause harm within the
    neck. Consequently do the exercises with form that is ideal and
    you may be properly on the way to building muscle.

  49. Bernardpem says:

    Interesting articles about pharmaceutical and different stories

  50. bzymdg says:

    Pulling articles about numb and unconventional stories