The Database
Storing user's data
Earlier, we touched on the Model (in the MVC Pattern) - the time has come to continue this topic. The model defines how the users data will be stored by your webapp. Think of it like defining the columns of a spreadsheet, a very rigid and inflexible spreadsheet. But once you define this spreadsheet, or database, you can store all sorts of stuff in it. Instead of writing static HTML pages all the time, you could create an HTML form that allows you to post new blog entries. The blog posts will be stored in the database and then retrieved whenever someone goes to read it.
Goal
By the end of this section, you should be able to understand the changes made in Checkpoint 4 - Interactive.
Justification
"But I like writing static HTML pages, and creating a database and dealing with forms and user's posting data sounds hard! Why should I?"
Well, for a few reasons. Having a database and forms allows for people to be able to interact with your webpage. Being able to Tweet or post a Status Update on Facebook requires the use of a database. Sure, you could still write all of your blog posts with just static HTML, but if you want to allow users to add comments you'd need a database. Using an HTML form also means that you don't have to push new changes to your production environment, you can just click 'Post' or something on a user friendly web page.
If you're still not convinced, if you don't want a blog or anything that allows for user's comments, or if your content is not going to change very frequently then Checkpoint 3.5 - Portfolio is exactly what you're looking for. Static sites have plenty of great use cases. They can be used as an e-Resume of sorts or as your central hub on the Internet that connects your separate profiles. In fact, this entire tutorial is one giant static website!
Pressing On
So you've stuck around to see what's the business? Great! Let's start by setting up your Model. Then we can hook it in with your View and Controller. Let's use the example of an online guestbook that anyone can sign to help illustrate what's going on.
When you're setting up your model, think of it like creating a spreadsheet where the only columns that can contain information have to be explicity defined by you. And every new blog post, comment, Tweet, or Status Update is a new row. Let's set up a model in Google's webapp2 database.
models.py
from google.appengine.ext import ndb
import settings
# Check if database name is specified in settings.py
try:
DB_NAME = settings.DB_NAME
except AttributeError:
DB_NAME = 'dev-guestbook'
# Gets the name of the database
def guestbook_key(guestbook_name=DB_NAME):
return ndb.Key('Guestbook', guestbook_name)
class Author(ndb.Model):
name = ndb.StringProperty(indexed=False)
email = ndb.StringProperty(indexed=False)
class Greeting(ndb.Model):
author = ndb.StructuredProperty(Author)
content = ndb.StringProperty(indexed=False)
date = ndb.DateTimeProperty(auto_now_add=True) # Automagically adds a date/time stamp when a new object is created
Let's ignore that first try/except block and the guestbook_key
function for a second. The Author
and Greeting class are what we care about. So what information do we want to keep for this online guestbook?
We'll need to keep track of the user's greeting. Now what information makes up a greeting? The message, the
author, and a date. The message can just be stored as a string, and the date can
be stored as a DateTime object. But let's say we want the author to store both a
name and an email address; easy we just create a new Author class. Now, the author in the Greeting will be of
type Author which will contain two strings, one for the name and one for the email. Now we have a single
object that we can create or read from that has all of the information necessary for a single greeting from
a user.
CRUD
Now let's take a quick detour to explain CRUD. CRUD stands for Create, Read, Update, and Delete and is an important practice when it comes to web architecture. Right now, we're only going to worry about two of them: Create and Read.
So how do you transport the data from the HTML page the user has just typed their greeting into, to your database? CRUD doesn't just apply to databases, it also exists in web protocols. But instead of Create it's called Post, and instead of Read it's called Get. Most of your web traffic is likely Get requests. Just by loading a webpage you're performing a Get request. Get requests are how you get any data in your web browser. And if you want to Create new data to be stored, you have to Post that data.
Think of it like this: when you load up your Twitter homepage, you're actually performing a Get request for all of that data. And that data is coming from a Read request from some database. When you have some bomb Tweet you want the world to see, you have to Post that data back to Twitter. And when Twitter recieves that Post, it performs a Create action back to the database.
The Front End
You're now armed with the knowledge of how most information travels over the web, as well as an example for how to layout your model. So let's set up the View to handle user input with a Form ready to Post. We'll also want to display the existing greetings.
views/greet.html
{% extends "base.html" %}
{% block title %}Greet{% endblock %}
{% block body %}
<h2>Greet</h2>
<hr />
<br />
<ul class="list-unstyled">
{% for greeting in greetings %}
<li style="padding-bottom: 20px;">
{{ greeting.content }}<br />
- {{ greeting.author }}
</li>
{% endfor %}
</ul>
<hr />
<form action="/greet" method="post">
<div class="form-group">
<label for="content">Sign</label>
<textarea class="form-control" id="content" name="content" rows="8"></textarea>
</div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="(Optional)">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" id="email" name="email" placeholder="(Optional)">
</div>
<button type="submit" class="btn btn-default">Sign Guestbook</button>
</form>
{% endblock %}
This uses an HTML form. And from Line 21, you can see that's it is ready to Post
the data when the user hit's the submit button. We'll want to make sure each of the input
and
textarea
elements have unique ids.
This HTML page will render 3 text boxes for the user to enter their greeting, name, and email, along with one submit button. When the button is clicked, a Post action will happen against the "/greet" endpoint (make sure "/greet" points to the correct controller in urls.py).
Tying It All Together
You've got the two sides all hooked up, but now we need to glue them together. The user inputs their data and it get's Posted, but it doesn't quite make it to the database yet. Enter Controller, stage center.
controllers/greet.py
from controllers.base import BaseRequestHandler
from models import DB_NAME
from models import guestbook_key
from models import Author
from models import Greeting
class Greet(BaseRequestHandler):
def get(self):
query = Greeting.query(ancestor=guestbook_key(DB_NAME)).order(-Greeting.date)
greetings_obj = query.fetch(10)
greetings = []
for greeting in greetings_obj:
if greeting.author and greeting.author.name:
author = greeting.author.name
elif greeting.author and greeting.author.email:
author = greeting.author.email
else:
author = "Anonymous"
greetings.append({
"author": author,
"content": greeting.content,
})
self.generate("greet.html", {"greetings": greetings})
def post(self):
greeting = Greeting(parent=guestbook_key(DB_NAME))
user_name = self.request.get('name') # from HTML element with id='name'
user_email = self.request.get('email') # from HTML element with id='email'
greeting.author = Author(name=user_name, email=user_email)
greeting.content = self.request.get('content') # from HTML element with id='content'
greeting.put()
self.redirect('/greet')
Notice how the controller for the 'Greet' page now has two functions! Their names are very special as well.
The get
function handles all Get requests, while the post
function handles all
Post actions.
When you first load the greet page, the get
function is executed. It performs a Read (or query)
on the database and gets a greetings_obj
. Then some logic is performed to get the desirable
author name, and then returns that greetings object to the 'greet' View to be displayed.
The second function, the post
function get's executed after a user has clicked that Submit button
on the page. It first creates a new Greeting object in the correct database. It gets the data from each of the
input elements from the form that was Posted, and then puts (or saves) the greeting to the database.