Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
780 lines (555 sloc) 37.2 KB

Building Web Applications in Django

In this workshop, we'll be developing a text sharing site (much like Pastebin) called Gluebin.

Some Background

What is Django?

Django is a powerful Python web framework designed for the rapid development of database-driven web applications. It implements the MVC design pattern like most other modern web frameworks.

Who's using Django?

We are! But there are much bigger names who run Django to support their services, namely Instagram, Pinterest, The Washington Times, and Bitbucket. By the end of the workshop, you should be able to add your name to the list!

A brief introduction to MVC

We've prepared some slides to get you familiarize you with MVC and how it applies oto Django. You can view them here.

Building Gluebin

Now that we're familiar with Django on the surface level, it's time to start building our application. Before we start writing code, we need to make sure our environment is set up correctly.

Setting up

Installing Python and virtualenv

If you're using Windows, please refer to Jonathan's Python and virtualenv installation guide to get setup with Python and virtualenv.

If you're running OSX or Linux, you should have Python and pip installed already, so you should only need to install virtualenv: pip install virtualenv.

Setting up a virtual environment

Now that virtualenv is installed, we need to create a virtual environment for our code to live in.

Go ahead and make a folder for your project wherever you'd like (we picked ~/projects/Gluebin) cd into it. From there, create your virtualenv with virtualenv env.

If you're using Windows, activate the environment with env\Scripts\activate. If you're using OSX or Linux, the command is source env/bin/activate.

Installing Django

Now that we're in our environment, we need to install Django.

We'll do that by typing pip install django==1.9.5. We'll be using Django 1.9.5 for this tutorial, which is the latest stable build, but to future-proof we'll specify a version number explicitly.

Starting our Django project

Now that we have Django installed and our environment is ready to go, we can start our project. Django provides a utility for setting up a project's folders and essential files.

In your terminal, run django-admin startproject gluebin.

Let's see what startproject created:

gluebin/
    manage.py
    gluebin/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Now let's cd into the newly created gluebin folder and run our application to verify Django has installed correctly.

$ cd gluebin
$ python manage.py runserver

Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

April 19, 2016 - 18:16:33
Django version 1.9.5, using settings 'gluebin.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

We're running! Open up your favorite web browser and navigate to localhost:8000. We won't worry about the unapplied migrations warning until we start working with a database, so we can ignore that for now. Quit the server by pressing CONTROL-C.

Django welcome screen

Creating an app

Django separates web applications into two parts: apps, which are modules of code that do things, and projects, which contain apps and configuration to make up a complete web application. Think about Facebook: you have friends, messages, notes, likes, etc. Those are all separate apps that make up the overall project.

Our project works with a single app, which we'll call pastes. We can use the django-admin utility to start a new app:

$ django-admin startapp pastes

Much like startadmin, startapp takes care of building the folder and files you'll need for writing an app. Our folder structure should look like this now:

gluebin/
    manage.py
    gluebin/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    pastes/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py

Welcome to Gluebin: Our first view

At this point, our setup is complete: it's time to start writing code. In its current state, our site doesn't do anything other than display the default Django welcome page. Let's change that.

Open up pastes/views.py in your favorite text editor.

from django.shortcuts import render

# Create your views here.

As you might be able to guess, this file houses the views for our pastes app. Django's telling us to create our views here, so let's write some.

Instant gratification is great, so we're just going to write a quick view to demonstrate how it works. For it, we'll be using django.http.HttpResponse.

from django.http import HttpResponse
from django.shortcuts import render

# Create your views here.
def index(request):
    return HttpResponse('Welcome to Gluebin!')

Line by line, this is what's happening:

  • First, we import Django's HttpResponse class. We'll use this to quickly render some text to the browser.

  • This was autofilled with us by startapp, but we don't need to worry about it now. We'll be using it later to render pages with HTML templates.

  • Now we'll define a function called index. This function takes in an HttpRequest object that's automatically created by Django when a browser requests a URL, and it contains information about the request that was sent to our server (its method, headers, etc.). Every view function takes in request as its first parameter.

  • Finally, we'll return an HttpResponse that greets our visitor with some text welcoming them to our site.

Go ahead and save the file.

URL routing

Before we can render our view, we need to tell our site's URL configuration about it. You may have noticed earlier that startproject built the file gluebin/urls.py. This represents the site's global URL configuration, but each app inside a project can contain a local URL configuration as well.

Go ahead and create the file pastes/urls.py:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

Here we'll map our views to URLs so that our server knows what to respond with when a certain URL is requested. Right now we only have one view, index (the same name as the function in views.py).

Most of our code here should be self-explanatory, with the exception of line 6:

url(r'^$', views.index, name='index'),

Breaking it down piece-by-piece, the url function takes three arguments:

  • The first, r'^$'. is the trickiest. Django's URL dispatcher uses regular expressions to match an incoming request to the appropriate URL. In this case, ^$ is a regular expression that starts and stops immediately -- essentially matching only the root level of the URL and nothing else.

  • Next, we pass in the view function that we wrote earlier. In line 3, you'll noticed we imported views.py, so we can just write views.index.

  • Finally, we provide an optional name argument so that we can refer to this URL within our code. We'll see this in action later.

Go ahead and save the file.

We've written a local URL configuration for the pastes app, but now we need to tell our site's URL configuration to use it. Go ahead and open up gluebin/urls.py that startproject created earlier.

"""gluebin URL Configuration
<snip>
"""
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

The startproject command automatically populates this file to use Django's built-in admin site, but we don't need that, so we can ignore it.

Instead, we want to include our pastes app's URL configuration:

from django.conf.urls import include, url

urlpatterns = [
    url(r'^', include('pastes.urls')),
]

You'll notice we imported the include function, and you'll see a familiar first argument to the url function.

This time, instead of ending our regular expression (with $), we leave it open. This will let us use our pastes app's URL configuration in the root directory of our site (localhost:8000/).

Go ahead and save this file.

Let's run our server again and load up our site in our web browser:

$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

April 20, 2016 - 03:28:14
Django version 1.9.5, using settings 'gluebin.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

If everything is right, you'll see "Welcome to Gluebin!" on the page instead of the initial Django start page. Now that you understand the basic concept, we can get into building Gluebin's real features. Quit the server again with CONTROL-C.

Using a database

Because we're going to be saving data, we'll need a database. Database configurations are loaded from our project's settings file, which we can find in gluebin/settings.py.

Settings

Let's open our settings file up and poke around a bit to get a feel for what it contains. There are a few fields that are worth mentioning before we get to our database:

  • SECRET_KEY: A salt used for generating hashes for encrypting data that the server uses (session keys, cross-site request forgery protection, password reset tokens, etc.). Your SECRET_KEY for a production server should never be made public.

  • DEBUG: A flag for turning debug mode on and off. When debug mode is enabled, error pages will display useful information about the error that occurred and the environment your site is running in. This should always be disabled in production.

  • INSTALLED_APPS: A list of apps that are being used by this project. We'll be adding to this in a moment.

  • DATABASES: A dictionary of databases that our application will use. We're only concerned about the default database and engine (SQLite) right now, but know that we can extend our application to use multiple databases and multiple engines (MySQL, Postgres, etc.).

Models

In the MVC paradigm, models represent data and the rules that apply to the data. In our case, the data we're interested in are snippets of code called pastes.

Go ahead and open up pastes/models.py. Much like in pastes/views.py, Django autogenerates a few lines of code to get you started:

from __future__ import unicode_literals

from django.db import models

# Create your models here.

Let's build a model to represent a paste:

from __future__ import unicode_literals

from django.db import models

# Create your models here.
class Paste(models.Model):
    content = models.TextField(max_length=5000)
    description = models.TextField(max_length=500, blank=True)
    post_date = models.DateTimeField(auto_now=True)

Looking at this line by line:

  • Each model in Django subclasses django.db.models.Model, so we need to include that in our class declaration.

  • Now we need to declare fields for our model. Let's consider what a paste might contain:

    • First, we need the text or code we're going to share. We'll call that content.
    • Next, we need a description to describe our paste. We'll call that description.
    • Finally, we're interested in when our paste was published. We'll call that post_date.
  • Now that we have the names of our fields, we need to give them types. Django provides a lot of field types to use, but we're only interested in two for our model.

  • Because we're sharing text, content should be a TextField. A description is also text, so we'll make that a TextField as well. We use the max_length argument to define a maximum size for a paste's content and description (5000 and 500 respectively) and then use the blank argument to allow descriptions to be empty strings.

  • Now we need to define a type for post_date. A date is best represented as a DateTimeField, so we'll set its type to that. We provide the auto_now argument and set it to true so that the field is updated to the current datetime every time the object is saved.

Note: There are many different fields to handle data. You can explore them here.

Save pastes/models.py.

Database migrations

Now that we've constructed a model, we'll be able to use it with Django's ORM to interact with the database.

Before we can do that, we'll need to register our app within our project. To do that, let's revisit gluebin/settings.py and take a look at our INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'pastes'
]

Notice that we've appended 'pastes' to the end of this list. By doing that, our site becomes aware of the pastes app and its models. Go ahead and save gluebin/settings.py.

We'll use Django to build our database structure with the commands makemigrations and migrate.

To start, we'll run python manage.py makemigrations pastes:

$ python manage.py makemigrations pastes

Migrations for 'pastes':
  0001_initial.py:
    - Create model Paste

The makemigrations command will create a new Python file in pastes/migrations. This Python file is loaded by Django when the migrate command is run and it's used to construct the fields in the database. Every time you make a change to a model's fields, you'll need to run makemigrations again before migrate will pick them up and modify your database structure.

Finally, we'll run python manage.py migrate to build our database structure:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, pastes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying pastes.0001_initial... OK
  Applying sessions.0001_initial... OK

You'll notice a few migrations in the list that aren't part of pastes: admin, contenttypes, auth, and sessions. These apps are included in INSTALLED_APPS by default when using the startproject command and they have database tables that they need to set up as well. These aren't relevant to our project, so we can ignore them.

Templates

Right now, our homepage only displays some plain text. That works for an introductory look at Django, but for our application we need to use HTML, CSS, and JavaScript.

We've looked at models, views (traditionally, these would be called controllers in MVC), and now we need to introduce templates (traditionally, these would be called views in MVC). Django's quirkiness aside, templates are responsible for displaying data to the user. We'll be creating three templates:

  • base.html: The basic layout and structure of our site.
  • index.html: The homepage of our site that allows users to submit new pastes.
  • view_paste.html: The page that displays an existing paste.

You can download those three templates and their assets here. Save them somewhere and we'll use them in a moment.

Setting up folders

The first thing we need to do is to create the folders that will house our templates (HTML) and our static assets (CSS, JS, images). In the real world, the location of these folders tends to differ between projects and is largely a matter of personal preference. We'll be sticking with the Django's official recommendation for our tutorial.

We're going to create a few folders:

In pastes, create a folder named templates and a folder named static.

Open up the location you saved the assets to earlier and copy the HTML files (in the html folder) into pastes/templates.

Next, copy the css, js folders from the assets folder you downloaded earlier and paste them into pastes/static. Copy background.jpg into pastes/static as well. At the end of it, your pastes app should look like this:

 pastes/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        static/
          background.jpg
          css/
            style.css
        js/
          line-numbers.js
        templates/
          base.html
          index.html
          view_paste.html
        tests.py
        views.py

Now that we've got everything in our project, we can start filling out our templates.

Template inheritance and static files

The first thing we need to do is get acquainted a concept called template inheritance.

Django's core philosophy is all about keeping things DRY ("Don't repeat yourself"). One of the main goals of the framework is to write concise, reusable code to increase the speed and ease of development. With templates, we can avoid repeating ourselves by using template inheritance.

We've created base.html to take care of the main design and layout of the site. Every page on our site has the same general structure and design. By taking care of those elements in one place, our index.html and view_paste.html templates only have to focus on doing what they're supposed to do -- providing a form and displaying data.

The first file we're interested in is base.html, so go ahead and open that up:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Gluebin - </title>

  <!-- Bootstrap CDN -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
  <link rel="stylesheet" type="text/css" href="#">
</head>
<body>
  <div class="container">
    <div class="content">
    </div>
  </div>
</body>
</html>

Right now, this is just a plain HTML file. We're making use of the Bootstrap CDN to make things look a little prettier, but right now it's incomplete. Let's fill it out and go over what's happening:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Gluebin - {% block title %}{% endblock %}</title>

  <!-- Bootstrap CDN -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
  <link rel="stylesheet" type="text/css" href="{% static "css/style.css" %}">
</head>
<body>
  <div class="container">
    <div class="content">
      {% block content %}{% endblock %}
    </div>
  </div>
</body>
</html>

The first thing you'll notice is {% load static %}. This loads the {% static %} tag you'll see in a second so that we can refer to relative paths instead of absolute paths for our content.

The next key section is {% block title %}{% endblock %}. This line will allow the templates that extend base.html to override the title of the web page (for example, "Gluebin - New Paste" is the title of the homepage).

Next up, we need to use tell base.html to use our custom stylesheet. Because it's located in the static folder, we use href="{% static "css/style.css" %}" to generate the path for it. {% static "path/to/file" %} is just shorthand that will allow us to move our static folder around without any hassle as long as Django knows where STATIC_URL is (a setting in settings.py).

Finally, we'll create another block to allow templates extending base.html to insert their own HTML into the content div by writing {% block content %}{% endblock %}.

Go ahead and save this file.

Extending base.html, writing our homepage

Now we need to build our homepage. Open up index.html:

<form method="POST" class="form-horizontal" action="">
    <h2>Create Paste</h2>
    <hr>
    <div class="form-group">
        <label for="content" class="col-sm-1 control-label">Content*</label>
        <div class="col-sm-offset-1 col-sm-9">
            <textarea name="content" class="form-control"></textarea>
        </div>
    </div>
    <hr>
    <div class="form-group">
        <label for="description" class="col-sm-1 control-label">Description</label>
        <div class="col-sm-offset-1 col-sm-9">
            <textarea name="description" class="form-control"></textarea>
        </div>
    </div>
    <hr>
    <button class="btn btn-primary submit">Submit</button>
</form>

Much like base.html, this is just raw HTML that doesn't do much. With Django's templating language, we can change that:

{% extends "base.html" %}

{% block title %}New Paste{% endblock %}

{% block content %}
<form method="POST" class="form-horizontal" action="">
    {% csrf_token %}
    <h2>Create Paste</h2>
    <hr>
    <div class="form-group">
        <label for="content" class="col-sm-1 control-label">Content*</label>
        <div class="col-sm-offset-1 col-sm-9">
            <textarea name="content" class="form-control"></textarea>
        </div>
    </div>
    <hr>
    <div class="form-group">
        <label for="description" class="col-sm-1 control-label">Description</label>
        <div class="col-sm-offset-1 col-sm-9">
            <textarea name="description" class="form-control"></textarea>
        </div>
    </div>
    <hr>
    <button class="btn btn-primary submit">Submit</button>
</form>
{% endblock %}

First, we need to tell our template to extend base.html, which we do in the first line: {% extends "base.html" %}.

Next, we'll override {% block title %}{% endblock %} from our base template and insert the title text for our page. Because the homepage is also the only place to submit forms, we'll just write New Paste.

The same thing happens below. We override {% block content %} {% endblock %} and insert our form into it.

The next template tag we come across is a new one: {% csrf_token %}. This tag generates a CSRF security token and places it in the form as a hidden input. This token prevents malicious websites from sending data to our server on behalf of logged in users, and is a requirement for Django views that accept POST data.

Go ahead and save the file.

Template variables and template logic

The last thing we need to do is write a template for displaying existing pastes and their data.

Open up view_paste.html:

<div class="form-horizontal">
    <h2>Viewing Paste #ID </h2>
    <p>Posted on: date</p>
    <hr>
    <div class="form-group">
        <label for="code" class="col-sm-1 control-label">Content</label>
        <div class="col-sm-offset-1 col-sm-9">
            <pre id="content"><code>Paste content</code></pre>
        </div>
    </div>
        <hr>
        <div class="form-group">
            <label for="description" class="col-sm-1 control-label">Description</label>
            <div class="col-sm-offset-1 col-sm-9">
               <pre>Paste description</pre>
            </div>
        </div>
</div>

<script src="line-numbers.js"></script>

Like before, this is just a skeleton for our template. We need to actually render dynamic data, and for that we need Django's template language.

Let's look at the completed template:

{% extends "base.html" %}
{% load static %}

{% block title %}Viewing Paste #{{ paste.id }}{% endblock %}

{% block content %}
<div class="form-horizontal">
    <h2>Viewing Paste #{{ paste.id }} </h2>
    <p>Posted on: {{ paste.post_date|date:'SHORT_DATE_FORMAT' }}</p>
    <hr>
    <div class="form-group">
        <label for="code" class="col-sm-1 control-label">Content</label>
        <div class="col-sm-offset-1 col-sm-9">
            <pre id="content"><code>{{ paste.content }}</code></pre>
        </div>
    </div>
    {% if paste.description|length > 0 %}
        <hr>
        <div class="form-group">
            <label for="description" class="col-sm-1 control-label">Description</label>
            <div class="col-sm-offset-1 col-sm-9">
               <pre>{{ paste.description }}</pre>
            </div>
        </div>
    {% endif %}
</div>

<script src="{% static "js/line-numbers.js" %}"></script>
{% endblock %}

There are a few old things that we've already seen (extending base.html, loading static assets, and overriding template blocks), but there are also new things to pay attention to:

  • {{ paste.id }}. Template variables in Django are wrapped with two braces and can be used to display dynamic data. You'll see in a few sections where we actually pass in our paste object, but for now you just need to know that paste is an instance of the Paste model we created earlier and we can access its fields the same way we would in Python. This line, for instance, grabs its id field. We'll do the same thing to display a paste's date, content, and description in a moment.

  • Next, you'll see a template variable with a pipe character (|). This specifies a template filter that can be used to filter a variable in some way. In this case, we filter the paste.post_date variable with Django's built in date filter and then provide it with an argument. This transforms the variable and renders it in a more human-readable format.

  • Templates can also contain logic (if-else and loops), which we see in {% if paste.description|length > 0 %}. This block ends at {% endif %} and is used to check if the paste's description is blank or not. If it is, we just don't bother rendering the description field at all.

Lastly, you'll see we use {% static %} to include a JavaScript file. This is just a quick hack to display line numbers.

Save the file and we can move on to the backend.

Writing our first real view

Our homepage looks great (we promise), but right now it's just an HTML file that Django doesn't know about. Our site still routes to our "Welcome to Gluebin!" view we wrote earlier. Let's fix that.

Open up pastes/views.py:

from django.http import HttpResponse
from django.shortcuts import render


def index(request):
    return HttpResponse("Welcome to Gluebin!")

We need to modify this file to render our template. For that, we'll use django.shortcuts.render:

from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request, 'index.html')

We're no longer using django.http.HttpResponse, so we can remove that import. You'll notice most of the code remains the same, except for the line where we return a response. Now, we're using the render function, which takes in the request object that Django generated when the URL was accessed and a relative path to a template to render. Django's template loader is intelligent and will automatically locate your templates folder as long as its within the project, so all we need to do is pass in index.html.

Go ahead and save this file and run your server again. We don't need to work with pastes/urls.py because the URL configuration is already setup to route to our index view.

If everything went well, you should see a homepage that's much more satisfying than before:

Gluebin's homepage

Let's quit our server again with CONTROL-C.

Forms: Handling data

Homepage designed? Check. Our application is just about finished--now all we need to do is make our form work and then write a view and URL for displaying the submitted paste data. Let's get our form working.

The first thing we need to do is create a file in our pastes app called forms.py. This file -- as you might be able to infer from the names of views.py and urls.py -- will house our form and the rules that control it.

Open up pastes/forms.py:

from django import forms


class NewPasteForm(forms.Form):
    content = forms.CharField(widget=forms.Textarea, max_length=5000)
    description = forms.CharField(widget=forms.Textarea, max_length=500, required=False)

As you get used to writing Django, you'll notice that a lot of the pieces of it follow a similar structure. Forms are no exception. They look pretty similar to models.

Once again, we'll step through the code line by line:

  • First, we import Django's forms class. This provides the framework for building forms, much like django.models provides the framework for models.

  • Next, we create a new class for our form, which we'll call NewPasteForm. This extends Django's forms.Form class so that we can use it as a form.

  • Now we need to define the fields we're accepting as inputs to our form. You'll notice that content and description match the name attribute of the HTML inputs we wrote in index.html. Those will be the names of the fields that are sent to the server when we submit our form. We use forms.CharField to tell our form that we're expecting text, widget=forms.Textarea to tell our form this is a textarea tag in HTML, and give it a max_length of 5000 to make sure we don't accept pastes that are bigger than the model we defined. We give description the argument required=False because descriptions are allowed to be blank (as per the model we defined).

Save pastes/forms.py.

Writing a view to handle POST requests

Right now, our HTML form in index.html doesn't submit data anywhere (the action attribute is just an empty string). We need to set up a view that can take in a POST request and do something meaningful with the data. In our case, that means inserting the data into our database.

Open up pastes/views.py:

from django.shortcuts import render, redirect

from models import Paste
from forms import NewPasteForm


# Create your views here.
def index(request):
    return render(request, 'index.html')

def new_paste(request):
    if request.method == "POST":
        form = NewPasteForm(request.POST)

        if form.is_valid():
            content = form.cleaned_data['content']
            description = form.cleaned_data['description']

            paste = Paste.objects.create(content=content, description=description)
            return redirect('view_paste', paste_id=paste.id)

    return redirect('index')

As you can see, we have a new view function, new_paste. Let's dissect our new code to see what's happening:

  • First, we import django.shortcuts.redirect. This will let us redirect a user to a different URL (remember the optional name argument from pastes/urls.py?).

  • Next, we import the Paste model we built. This will let us use create new pastes with Django's ORM.

  • After that, we import the form that we just built in pastes/forms.py. This will let us validate the data in our form and "clean" the data. It's important to note that cleaning isn't a matter of security like PHP's mysql_real_escape_string, it simply converts formdata into the appropriate Python type (CharFields to unicode strings, IntegerFields to ints, etc.).

  • Next, we check the method of the incoming request. Because we're sending a POST request to our form, that's the one we're interested in.

  • Now we initialize our form object. We pass in request.POST, a dictionary of the fields sent in the POST request, to the constructor.

  • The next step is to validate the form. form.is_valid() will check our incoming POST data with list of prebuilt rules for each field we defined in NewPostForm. We specified a max_length of 5000 for our content field, so if the length of the content data sent to the server is 5001, form.is_valid() will return false and we won't worry about inserting the new paste into our database.

  • In the next two lines, we access the form's cleaned_data to get the data we'll insert into our database.

  • Then, we create a new Paste object with Django's ORM by calling Paste.objects.create(). The arguments we pass in are the same as the names of the fields we defined in pastes/models.py. The ORM will automatically insert the record into the database.

  • Finally, we redirect to the view_paste URL we'll be making in a minute. This will redirect the user immediately to the paste they created.

  • We close it out with return redirect('index') so that the user is redirected to the homepage if they didn't send data or invalid form data was submitted.

Save the file and let's move on.

More URLs

Now that our view is constructed, we need to map it to a URL. We'll do this the same way that we did earlier on with our index view.

pastes/urls.py:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^pastes/new$', views.new_paste, name='new_paste'),
]

Looking good -- now we have a view that can take in form submissions and create entries in a database. The last step is to write a view for displaying our view_paste.html template and map it to a URL. While we're in our URL configuration, let's go ahead and route the URL for viewing a paste:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^pastes/new$', views.new_paste, name='new_paste'),
    url(r'^pastes/(?P<paste_id>[0-9]+)$', views.view_paste, name='view_paste'),
]

This line is a little scarier than the ones before it, but that's because it does more. We want to be able to view pastes at http://localhost:8000/pastes/:id, and to do that, we need to construct a more complicated regex:

r'^pastes/(?P<paste_id>[0-9]+)$'

This pattern will match when a user navigates to pastes/[some number], and Django's URL dispatcher will automatically take the number they input and pass it to our view function as the parameter paste_id. The [0-9]+ part of the string will match any integer string (an id). The ?P<paste_id>) bit surrounding it tells Django's URL dispatcher that this is a parameter with the name paste_id that should be passed to our view function.

Now that we've written our URL pattern, it's time to write our view.

Displaying pastes

We're almost there! The last step is to write a view that will grab a paste object from the database and then render it with the view_paste.html template we made earlier.

Add the bottom of pastes/views.py, add the following:

def view_paste(request, paste_id):
    try:
        paste = Paste.objects.get(id=paste_id)
    except:
        return redirect('index')

    return render(request, 'view_paste.html', {'paste': paste})

This view function takes a request argument like the rest, but it also takes in a paste_id argument, which is new to us. Recall that we set up our URL pattern to pass in a parameter named paste_id to our view function. It then attempts to retrieve the paste object from the database (redirecting to the homepage if this fails) and then renders the template.

Our return line looks a little different now, and that's because we've introduced context.

Context allows for views to pass data to a template so that the templating engine can use that data to output HTML. The render function provides an optional positional argument for context that expects a dictionary. In this case, we're only passing in one object, paste, but because it's a Python dictionary we can pass in any number of objects for the template to use. The keys of the dictionary are what end up being referenced in the template, but all fields of the values of the dictionary can be accessed (for example, we use {{ paste.content }} in our template).

A quick change to our form

Now that our views and URLs are completely finished, we need to tell our homepage's form where to submit data.

Open our index.html and let's give our form an action:

<form method="POST" class="form-horizontal" action="{% url 'new_paste' %}">

Django's template engine provides a way to dynamically place a URL into a template. Instead of worrying about relative paths, using the {% url %} template tag will automatically insert the appropriate link wherever its used. It works the same way the redirect function does for views -- you provide the optional name argument you created in your URL pattern. Because we want to send data to our new_paste view, that's the URL we use.

Save the file and pat yourself on the back. We're at the end!

Gluebin: Our very own Pastebin clone

Let's run our server one last time and test our site. We should be able to submit new pastes and view existing ones.

It's time to go full meta and create a paste that demonstrates how to use Django to display a paste object. Open up your view_paste.html template and copy it into the content textarea and then give it whatever description you want. Click submit and you should be redirected to the paste.

Viewing our first paste!

Congratulations, you've built your first Django application! If you liked Django, please check out Django's official site for more in-depth information. The /r/django and /r/djangolearning subreddits are great resources as well.