Getting Started With MongoDB (Python)

Now that I’ve extolled the virtues of using a NoSQL database, it’s time to put my money where my mouth is and show you how to use one.

To start things off, we’re going to build a super-simple site using the excellent Bottle Python framework, which is built for building simple sites like this.

This tutorial assumes that you already have a mongoDB instance up and running somewhere; if you haven’t done that already, take a look at the quickstart on mongodb.org.

First, download and install Bottle (it’s pretty easy). Once that’s done, we can start working on building our mini project.

Today we’re going to build a small ‘army manager’ site; users will be able to add, edit, and delete members of their army, and everything we need to track for the user will be stored in our mongodb database.

To start off, we’ll just make the index page return a template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import bottle, random
from bottle import route, jinja2_view as view, debug, run
 
DATABASE_HOST = 'localhost'
DATABASE_NAME = 'army'
DATABASE_PORT = 27017
 
import pymongo
from pymongo import Connection
connection = Connection(DATABASE_HOST, DATABASE_PORT)
db = connection[DATABASE_NAME]
users = db.users
 
SOLDIER_NAMES = ["Moe", "Curly", "Janne", "Peter", "Paul", "Kyle", "Chris", "Jude", "Larry", "Gus"]
 
@route('/')
@view('templates/index.html')
def index():
    current_user = get_current_user()
    return {
        'user': current_user,
    }
def get_current_user():
    ip = request.environ['REMOTE_ADDR']
    current_user = users.find_one({'ip': ip})
    if not current_user:
        users.insert({
            'ip': ip,
            'army': [],
        })
        current_user = users.find_one({'ip': ip})
    return current_user    
 
if __name__ == '__main__':
    debug(True)
    run(host='localhost', port=8000, reloader=True)

You may have noticed that instead of using the default template renderer, I used the Jinja renderer instead – which templating engine you use is up to you, but I prefer Jinja.

There’s a lot going on here – and if you’re new to Python, this code probably looks pretty scary. However, it’s pretty simple once you get the hang of it – here’s what we’re doing:

  • First, we’re importing all of the libraries we need, and setting up some of our configuration. This could be split out into another file easily enough – but for the sake of this tutorial, we’re going to keep everything self-contained for now. The libraries and helpers we’ve imported are random, bottle.route, bottle.run, bottle.request, bottle.jinja2_view as view, bottle.response, bottle.debug, bottle.redirect, pymongo, and pymongo.Connection

    Most of these come from the Bottle library, and are designed to help us get our app off the ground faster – we imported random to be able to choose names for our soldiers, and pymongo to help us work with our MongoDB database.

  • The next thing we did was create our index view – by saying that Bottle should route the url ‘/’ to our ‘index’ function. As you can see, all our index function does is retrieve the current user, and return it so that we can render it with our template – which we specified using our view() decorator.

  • The next thing we do is define a function to retrieve the current user, based on their IP address – this is our first dive into working with MongoDB. First, we retrieve the IP address of the computer that’s requesting the page, so that we can track the visitor’s information. Once we have that, we query our users database, to see if a user exists with our IP – if there is no user with the IP address we’re testing for, we create a new user with an empty army, and then return that one.

  • Finally, we have a little bit of utility code to let us test our Bottle app. This piece of code allows us to run our app by navigating to it in the terminal, and then just running “python app.py” – assuming your MongoDB instance is up and running and your dependencies are okay, you should be able to check out your app by navigating to http://localhost:8000/.

If you navigate to your app right now though, you might notice that there’s a problem – we don’t have an index template! We’ll fix that by creating a template for our app, under templates/index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
       "http://www.w3.org/TR/html4/strict.dtd">
 
    <html lang="en">
    <head>
    	<title>Python MongoDB Tutorial</title>
    </head>
    <body>
        Your Army: {{ user.army }}
        <form action='/add-soldier/' method='post'>
            <input type="submit" value="Add Soldier" />
        </form>
        <form action='/remove-soldier/' method='post'>
            <input type="submit" value="Remove Soldier" />
        </form>
        <form action='/rename-army/' method='post'>
            <input type="submit" value="Rename All Soldiers" />
        </form>
    </body>
    </html>

As you can see, we’re not shattering any barriers with this template – but it lets us test our app, so it will do for now. Navigate again to http://localhost:8000/, and you should see something like this:

Your Army: [], Add Soldier button, Remove Soldier button, Rename All Soldiers button

With our template all set up, it’s time to write our views – starting with the view that lets us add soldiers:

@route('/add-soldier/', method='POST')
def add_soldier():
    current_user = get_current_user()
    army = current_user.get('army', [])
    army.append(random.choice(SOLDIER_NAMES))
    users.update(
        {'ip': current_user['ip']},
        {'$set': {
            'army': army,
        }}
    )
    redirect('/')

For the most part, this view’s pretty simple – we want it to be at the url ‘/add-soldier/’, and only responding when users POST to it (by clicking on the ‘add soldier’ button in our template). When a user clicks on the button and POSTs to this view, we retrieve their current army (making sure to default to [] if it doesn’t exist – this is important!), add a new soldier to it (by appending a random name from our SOLDIER_NAMES variable), and then update the user’s information in our MongoDB database. Finally, we redirect back to the frontpage so that the user can see their new army.

It’s important to keep in mind that when you’re using a schema-less database like MongoDB, you may not actually have the properties you’re expecting to, especially if you haven’t set them. With that in mind, always be sure to write your code in such a way that it will still work if a an object doesn’t have the property that you were expecting it to.

If you visit your testing page at http://localhost:8000/ now and click the ‘Add Soldier’ button, you should notice that your army has increased – there’s a new name there!

Index page with a new army member

Now that we can add soldiers to our army, we need to be able to remove them as well. Let’s write a quick view to do that:

@route('/remove-soldier/', method='POST')
def remove_soldier():
    current_user = get_current_user()
    army = current_user.get('army', [])
    if len(army) > 0:   # make sure we don't pop from an empty list
        army.pop()
    users.update(
        {'ip': current_user['ip']},
        {'$set': {
            'army': army,
        }}
    )
    redirect('/')

This view is just about the same as our add_soldier view from earlier, except that we’re removing soldiers instead of adding them before we write the user’s army into our database. We make sure they actually have soldiers in their ‘army’ array, and then we remove one if they do before writing their army back to the database, and redirecting back to the front page.

Index page, with an empty army

With that view written, there’s only one more to write – this one will live at ‘/rename-army/’, and update the names of all the soldiers in the user’s army:

@route('/rename-army/', method='POST')
def rename_army():
    current_user = get_current_user()
    army = current_user.get('army', [])
    army = [random.choice(SOLDIER_NAMES) for i in army]
    users.update(
        {'ip': current_user['ip']},
        {'$set': {
            'army': army,
        }}
    )
    redirect('/')

As you can see, there isn’t much changed in this view either – we retrieve the user and their army, update the value of their ‘army’ array, write it to the database, and then redirect them. We use a list comprehension to update the name of every one of our army members, and that’s all that’s special here.

With that, we’re done! If you visit http://localhost:8000/, you should be able to add soldiers, remove soldiers, and rename soldiers – with all the data being stored in your MongoDB database.

If you’re stuck, or can’t seem to get it going, you can download the code for this tutorial at http://buildingbrowsergames.com/blog/wp-content/downloads/mongodb-python-1.zip.

Extra Homework

  • Our views involved writing a lot of boilerplate code, which can probably be refactored. Write a custom User object that handles storage and retrieval, along with making it easier to interact with the user’s army.

  • Modify the ‘army’ array to store custom Soldier objects, which have a name and an attack value – when a user adds a soldier to their army, randomize both values (some of the work for this has been done for you – see the download above).

Wish there was more?

I'm considering writing an ebook - click here.

.

Luke is the primary editor of Building Browsergames, and has written a large portion of the articles that you read here. He generally has no idea what to say when asked to write about himself in the third person.

Tags: ,

Monday, February 8th, 2010 tutorial
  • Andrew

    Strangely the redirect portion of /add-soldier/ doesn't work for me. In google chrome it directs
    me to the link is broken page and firefox asks me if I want to resend POST data and then ends
    up on an empty page. Not sure what I'm doing wrong.

  • I've noticed this issue with some of my Bottle projects before, but I'm not
    exactly sure what's causing it - and it's been too intermittent for me to
    reliably pin it down. I'll do some research and let you know if I find a
    fix.

  • If you are using 'ip' as the primary key to the users collection you may want to call it '_id'. Every object has an _id that is generated if you don't supply one. Also there is automatically a unique index on _id which will prevent you from creating two copies of the user.

  • Thanks for the heads-up, Mathias - I'll have to keep that in mind for
    projects I write in the future.

  • Great post! You might want to take a look at some of the other atomic update modifiers that MongoDB provides. For example, the add-soldier view could be written something like this:

    def add_soldier():
    current_user = get_current_user()
    users.update(
    {'ip': current_user['ip']},
    {'$push': {
    'army': random.choice(SOLDIER_NAMES),
    }}
    )
    redirect('/')

    Using $push will work even if the list doesn't exist to start with.

    You could get even more advanced by doing an upsert, eliminating the need to check if the user exists in get_current_user - just grab the IP and do the update posted above (with upsert=True added as a kwarg). That will be just a single fire-and-forget operation for the entire request.

    Anyway, already looks great as is - thanks for the post!

  • Hi Mike,

    Thanks for the heads-up - I knew about the upsert, but I didn't know
    about the $push operation (or think to use it in my get_current_user
    func). Thanks for the heads-up!

blog comments powered by Disqus

About

Building Browsergames is a blog about browsergames(also known as PBBG's). It's geared towards the beginner to intermediate developer who has an interest in building their own browsergame.

Sponsors

Got Something to Say?

Send an e-mail to luke@buildingbrowsergames.com, or get in touch through Twitter at http://twitter.com/bbrowsergames