A newer version of this documentation is available.

View Latest

Managing beers

First we’ll show the construction of the web app with respect to managing beers.

We’ll be able to list, inspect, edit, create, search, and delete beers.

Showing Beers

Now we’re finally getting into the cooler stuff of this tutorial. First, we’ll implement several classes for our pages to use.

beer.py (custom Beer row class and processing)
class Beer(object):
    def __init__(self, id, name, doc=None):
        self.id = id
        self.name = name
        self.brewery = None
        self.doc = doc

    def __getattr__(self, name):
        if not self.doc:
            return ""
        return self.doc.get(name, "")

class BeerListRowProcessor(object):
    """
    This is the row processor for listing all beers (with their brewery IDs).
    """
    def handle_rows(self, rows, connection, include_docs):
        ret = []
        by_docids = {}

        for r in rows:
            b = Beer(r['id'], r['key'])
            ret.append(b)
            by_docids[b.id] = b

        keys_to_fetch = [ x.id for x in ret ]
        docs = connection.get_multi(keys_to_fetch, quiet=True)

        for beer_id, doc in docs.items():
            if not doc.success:
                ret.remove(beer)
                continue

            beer = by_docids[beer_id]
            beer.brewery_id = doc.value['brewery_id']

        return ret

First, we declare a simple Beer object. This app isn’t too fancy and we could’ve just used a simple dict. However, it allows us to demonstrate the use of the RowProcessor interface.

In the beer listing page, we want to display each beer along with a link to the brewery that produces it. However, we’ve defined the beer/by_name view to return only the name of the beer. To obtain the brewery, we need to fetch each beer document and examine it. The document contains the Brewery ID that we need later.

The BeerListRowProcessor is an implementation of the RowProcessor interface that operates on the returned view rows: For each raw JSON row, it creates a new Beer object. The first argument is the document ID, which is used to provide a link to display more information about the beer. The second argument is the name of the beer itself, which we use in the beer list on the webpage.

We also create a local variable called by_docids that allows us to get a Beer object by its document ID.

After we’ve created all the beers, we create a list of document IDs to fetch by using list comprehension. We pass this list to get_multi (passing quiet=True, because there might be some inconsistencies between the view indexes and the actual documents). While we could have made this simpler by performing an individual get on each beer.id, that would be less efficient in terms of network usage.

Now that we have the beer documents, it’s time to set each beer’s brewery_id to its relevant value: We first check to see that each document was successful in being retrieved; then we look up the corresponding Beer object by getting it from the by_docids dictionary using the beer_id as the key; then, we extract the brewery_id field from the document and place it into the Beer object.

Finally, we return the list of populated beers. The View object (returned by the query function) now yields results as we iterate over it.

Before we forget, let’s put this all together:

beer.py (showing beer listings)
@app.route('/beers')
def beers():
    rp = BeerListRowProcessor()
    rows = db.query("beer", "by_name",
                    limit=ENTRIES_PER_PAGE,
                    row_processor=rp)

    return render_template('beer/index.html', results=rows)

This code tells Flask to route requests to /beers to this function. Inside the function it creates an instance of the BeerListRowProcessor class we just defined; finally it executes a view query using the query method, passing it the name of the design and view ( beer and by_name, respsectively). We set the limit directive to the aforementioned ENTRIES_PER_PAGE directive, to avoid flooding a single webpage with many results. The last parameter in the query method call instructs the client use our own BeerListRowProcessor for processing the results.

At the end, the function directs the template engine to render the beer/index.html template, setting the template variable rows to the iterable returned by the query function.

Here is the beer/index.html template:

templates/beer/index.html
{% extends "layout.html" %}
{% block body %}

<h3>Browse Beers</h3>
<form class="navbar-search pull-left">
    <input id="beer-search" type="text" class="search-query" placeholder="Search for Beers">
</form>


<table id="beer-table" class="table table-striped">
    <thead>
        <tr>
            <th>Name</th>
            <th>Brewery</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        {% for beer in results %}
        <tr>
            <td><a href="/beers/show/{{beer.id}}">{{beer.name}}</a></td>
            <td><a href="/breweries/show/{{beer.brewery_id}}">To Brewery</a></td>
            <td>
                <a class="btn btn-small btn-warning" href="/beers/edit/{{beer.id}}">Edit</a>
                <a class="btn btn-small btn-danger" href="/beers/delete/{{beer.id}}">Delete</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<div>
    <a class="btn btn-small btn-success" href="/beers/create">Add Beer</a>
</div>

{% endblock %}

We’re using Jinja {% for %} blocks to iterate and emit a fragment of HTML for each Beer object returned by the query.

Navigate to localhost:5000/beers, to see a listing of beers. Each beer has To Brewery ,Edit , andDelete buttons.

On the bottom of the page, you can also see an Add Beer button, which allows you to define new beers.

Let’s implement the Delete button next!

Deleting Beers

Due to the simplicity of Couchbase and Flask, we can implement a single method to delete both beers and breweries.

beer.py (deleting a beer)
@app.route('<otype>/delete/<id>')
def delete_object(otype, id):
    try:
        db.delete(id)
        return redirect('/welcome')

    except NotFoundError:
        return "No such {0} '{1}'".format(otype, id), 404

Here we tell Flask to route any URL that has as its second component the string delete to this method. The paths in <angle brackets> are routing tokens that Flask passes to the handler as arguments. This means that URLs such as /beers/delete/foobar and /foo/delete/whatever are all routed here.

When we get an ID, we try to delete it by using the delete method in a try block. If successful, we redirect to the welcome page, but if the key does not exist, we return with an error message and a 404 status code.

You can now access this page by going to localhost:5000/beers/delete/nonexistent and get a 404 error. Or you can delete a beer by clicking on one of the Delete buttons in the /beers page!

If you find that a beer is still displayed after you click the delete button, you can refresh the browser page to verify that the beer has been deleted.

Another way to verify that a beer has been deleted is by clicking the delete button again and getting a 404 error.

Displaying Beers

Here we demonstrate how you can display the beers. In this case, we display a page showing all the fields and values of a given beer.

beer.py (showing a single beer)
@app.route('/beers/show/<beer_id>')
def show_beer(beer_id):
    doc = db.get(beer_id, quiet=True)
    if not doc.success:
        return "No such beer {0}".format(beer_id), 404

    return render_template(
        'beer/show.html',
        beer=Beer(beer_id, doc.value['name'], doc.value))

Like for the delete action, we first check to see that the beer exists. We are passed the beer ID as the last part of the URL - this is passed to us as the beer_id.

In order to display the information for the given beer ID, we simply call the connection’s get method with the beer_id argument. We also pass the quiet parameter so that we don’t receive an exception if the beer does not exist; we then check to see that the success property of the returned Result object is true: If it isn’t then the beer does not exist and we return an HTTP 404 error; otherwise we construct a new Beer object; passing it the ID and the name field within the value dictionary.

At the end, the Beer object is passed to the templates/beer/show.html template which we’ll show here:

templates/beer/show.html
{% extends "layout.html" %}
{% block body %}

{% set display = beer.doc %}
{% set brewery_id = display['brewery_id'] %}

<h3>Show Details for Beer "{{beer.name}}"</h3>
<table class="table table-striped">
    <tbody>
        <tr>
            <td><strong>brewery_id</strong></td>
            <td><a href="/breweries/show/{{brewery_id}}">{{brewery_id}}</a></td>
        </tr>
        {% for k, v in display.items() if not k == "brewery_id" %}
        <tr>
            <td><strong>{{k}}</strong></td>
            <td>{{v}}</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<a class="btn btn-medium btn-warning"
    href="/beers/edit/{{beer.id}}">Edit</a>
<a class="btn btn-medium btn-danger"
    href="/beers/delete/{{beer.id}}">Delete</a>

{% endblock %}

Here we make the display variable in a special {% set %} directive. This makes dealing with the rest of the code simpler.

The next thing we do is extract the brewery_id, and create a special entry with a link pointing to the page to display the actual brewery.

Then we is iterate over the rest of the fields (omitting the brewery ID), printing out the key and value of each.

Finally, we provide links at the bottom to Edit and Delete the beer.

Editing Beers

beer.py (beer edit page)
def normalize_beer_fields(form):
    doc = {}
    for k, v in form.items():
        name_base, fieldname = k.split('_', 1)
        if name_base != 'beer':
            continue

        doc[fieldname] = v

    if not 'name' in doc or not doc['name']:
        return (None, ("Must have name", 400))

    if not 'brewery_id' in doc or not doc['brewery_id']:
        return (None, ("Must have brewery ID", 400))

    if not db.get(doc['brewery_id'], quiet=True).success:
        return (None,
                ("Brewery ID {0} not found".format(doc['brewery_id']), 400))

    return doc, None

@app.route('/beers/edit/<beer>', methods=['GET'])
def edit_beer_display(beer):
    bdoc = db.get(beer, quiet=True)
    if not bdoc.success:
        return "No Such Beer", 404

    return render_template('beer/edit.html',
                           beer=Beer(beer, bdoc.value['name'], bdoc.value),
                           is_create=False)


@app.route('/beers/edit/<beer>', methods=['POST'])
def edit_beer_submit(beer):
    doc, err = normalize_beer_fields(request.form)

    if not doc:
        return err

    db.upsert(beer, doc)
    return redirect('/beers/show/' + beer)

We define two handlers for editing. The first handler is the GET method for /beers/edit/<beer>, which displays a nice HTML form that we can use to edit the beer. It passes the following parameters to the template: the Beer object and a Boolean that indicates this is not a new beer (because the same template is also used for the Create Beer form).

The second handler is the POST method, which validates the input. The post handler calls the normalize_beer_fields function, which converts the form fields into properly formed names for the beer document, checks to see that the beer has a valid name, and checks to see that a brewery_id is specified and that it indeed exists. If all the checks pass, the function returns a tuple of ( doc, None ). The POST handler checks whether the second element of the returned tuple is false. If it is not false, then it’s an error code, and the first element is the error message. Otherwise, the first element is the document. It then sets the document in Couchbase by using the set method.

The template is rather wordy because we enumerate all the possible fields with a nice description.

templates/beer/edit.html
{% extends "layout.html" %}
{% block body %}

{% if is_create %}
<h3>Create Beer</h3>
{% else %}
<h3>Editing {{beer.name}}</h3>
{% endif %}

<form method="post" action="">
  <fieldset>
    <legend>General Info</legend>
    <div class="span12">
      <div class="span6">
        <label>Name</label>
        <input type="text" name="beer_name" placeholder="The name of the beer." value="{{beer.name}}">

        <label>Description</label>
        <input type="text" name="beer_description" placeholder="A short description." value="{{beer.description}}">
      </div>
      <div class="span6">
        <label>Style</label>
        <input type="text" name="beer_style" placeholder="Bitter? Sweet? Hoppy?" value="{{beer.style}}">

        <label>Category</label>
        <input type="text" name="beer_category" placeholder="Ale? Stout? Lager?" value="{{beer.category}}">
      </div>
    </div>
  </fieldset>
  <fieldset>
    <legend>Details</legend>
    <div class="span12">
      <div class="span6">
        <label>Alcohol (ABV)</label>
        <input type="text" name="beer_abv" placeholder="The beer's ABV" value="{{beer.abv}}">

        <label>Biterness (IBU)</label>
        <input type="text" name="beer_ibu" placeholder="The beer's IBU" value="{{beer.ibu}}">
      </div>
      <div class="span6">
        <label>Beer Color (SRM)</label>
        <input type="text" name="beer_srm" placeholder="The beer's SRM" value="{{beer.srm}}">

        <label>Universal Product Code (UPC)</label>
        <input type="text" name="beer_upc" placeholder="The beer's UPC" value="{{beer.upc}}">
      </div>
    </div>
  </fieldset>
  <fieldset>
    <legend>Brewery</legend>
    <div class="span12">
      <div class="span6">
        <label>Brewery</label>
        <input type="text" name="beer_brewery_id" placeholder="The brewery" value="{{beer.brewery_id}}">
      </div>
    </div>
  </fieldset>
  <div class="form-actions">
      <button type="submit" class="btn btn-primary">Save changes</button>
  </div>
</form>

{% endblock %}

The template first checks the is_create variable. If it’s False, then we’re editing an existing beer, and the caption is filled with that name. Otherwise, it’s titled as Create Beer.

Creating Beers

Creating beers is largely the same as editing beers:

beer.py (create beer page)
@app.route('/beers/create')
def create_beer_display():
    return render_template('beer/edit.html', beer=Beer('', ''), is_create=True)


@app.route('/beers/create', methods=['POST'])
def create_beer_submit():
    doc, err = normalize_beer_fields(request.form)

    if not doc:
        return err

    id = '{0}-{1}'.format(doc['brewery_id'],
                          doc['name'].replace(' ', '_').lower())

    try:
        db.insert(id, doc)
        return redirect('/beers/show/' + id)

    except KeyExistsError:
        return "Beer already exists!", 400

Here we display the same form as the one for editing beers, except we set the is_create parameter to True, and pass an empty Beer object. This is necessary because the template still tries to populate the form fields with existing values.

In the POST handler, we call normalize_beer_field as above when editing beers.

Because we’re creating a new beer, we use the add method instead. This raisew an exception if the beer already exists. We catch this and display it to the user.

If everything went well, the user is redirected to the beer display page for the newly created beer.

Searching Beers

In the beer listing page above, you might have noticed a search box at the top. We can use it to dynamically filter our table based on user input. We’ll use Javascript at the client layer to perform the querying and filtering, and views with range queries at the server (Flask) layer to return the results.

Before we implement the Python-level search method, we need to put the following in the static/js/beersample.js file (if it’s not there already) to listen on search box changes and update the table with the resulting JSON (which is returned from the search method):

static/js/beersample.js (snippet)
$(document).ready(function() {

    /**
     * AJAX Beer Search Filter
     */
    $("#beer-search").keyup(function() {
       var content = $("#beer-search").val();
       if(content.length >= 0) {
           $.getJSON("/beers/search", {"value": content}, function(data) {
               $("#beer-table tbody tr").remove();
               for(var i=0;i<data.length;i++) {
                   var html = "<tr>";
                   html += "<td><a href=\"/beers/show/"+data[i].id+"\">"+data[i].name+"</a></td>";
                   html += "<td><a href=\"/breweries/show/"+data[i].brewery+"\">To Brewery</a></td>";
                   html += "<td>";
                   html += "<a class=\"btn btn-small btn-warning\" href=\"/beers/edit/"+data[i].id+"\">Edit</a>\n";
                   html += "<a class=\"btn btn-small btn-danger\" href=\"/beers/delete/"+data[i].id+"\">Delete</a>";
                   html += "</td>";
                   html += "</tr>";
                   $("#beer-table tbody").append(html);
               }
           });
       }
    });
});

The code waits for keyup events on the search field, and if they happen, it issues an AJAX query on the search function within the app. The search handler computes the result (using views) and returns it as JSON. The JavaScript then clears the table, iterates over the results, and creates new rows.

The search handler looks like this:

beer.py (ajax search response)
def return_search_json(ret):
    response = app.make_response(json.dumps(ret))
    response.headers['Content-Type'] = 'application/json'
    return response

@app.route('/beers/search')
def beer_search():
    value = request.args.get('value')
    q = Query()
    q.mapkey_range = [value, value + Query.STRING_RANGE_END]
    q.limit = ENTRIES_PER_PAGE

    ret = []

    rp = BeerListRowProcessor()
    res = db.query("beer", "by_name",
                   row_processor=rp,
                   query=q,
                   include_docs=True)

    for beer in res:
        ret.append({'id' : beer.id,
                    'name' : beer.name,
                    'brewery' : beer.brewery_id})

    return return_search_json(ret)

The beer_search function first extracts the user input by examining the query string from the request.

It then creates a Query object. The Query object’s mapkey_range property is set to a list of two elements; the first is the user input, and the second is the user input with the magic STRING_RANGE_END string appended to it. This form of range indicates that all keys that start with the user input ( value ) are returned. If we just provided a single element, the results would also contain matches that are lexically greater than the user input; if we just provided the same value for the second and first elements, only items that match the string exactly are returned.

The special STRING_RANGE_END is actually a u"\u0FFF" UTF-8 character, which for the view engine means "end here." You need to get used to it a bit, but it’s actually very neat and efficient.

We re-use our BeerListRowProcessor class to filter the results here (because the data required is the same as that of the beer listing ( beer/index.html ) page.

However we need to return a JSON array of

{ "id" : "beer_id", "name" : "beer_name", "brewery" : "the_brewery_id" }

so we need to convert the rows into JSON first. This is done by the return_search_json function.

Now your search box should work nicely.