Let's Take Notes With Django - Part 3

Let's Take Notes With Django - Part 3

part of the Startproject To Deployment - Django Tutorial series

show all in series

back to index

Views define the logic our application will use to respond to requests - and in Django, the magic of class-based views make a lot of things automagic. This is ... well, there's an argument for class-based views, and to be fair I'm in the minority when I say I don't like them. But I don't, and I only use them very sparingly. I just feel like they obfuscate too much stuff in favor of magic that can be very convenient - but sometimes very inflexible as well. I have yet to run into an issue using function-based views, but class-based views seem to be a constant source of headache for me. Evaluate both for yourself, but in this tutorial series we're getting functional.

I'm a big fan of keeping application logic separate from core logic, and of keeping applications as loosely coupled as possible. It makes things more "plug-and-play" - after you've written several applications for different projects, you can play around with mixing and matching them to create new and awesome things. For this reason, I like the views.py file for any given app to handle views and model logic for that application only. This isn't always possible, but it's nice to keep things as separate as possible. This means we don't want our notes app dealing with the logic needed to display our website's home page, handle logging in and logging out, and stuff like that - we just want the notes app to handle notes. Long story short: we'll need to create a views.py inside our core djangonote/djangonote directory.

The views.py file that Django has created for us waits inside our notes directory, and looks like this right now:

from django.shortcuts import render
# Create your views here.

There's wasn't a views.py file inside our core djangonote/djangonote directory, but there is a urls.py file in there. Conversely, there's not a urls.py inside our notes directory right now. Let's even things out: touch views.py inside the djangonote_project/djangonote/djangonote directory if you haven't already, and touch urls.py inside the djangonote_project/djangonote/notes directory.

Let's open up the views.py file inside our core djangonote/djangonote directory, which is blank since we just created it. We first need to create a view to display our homepage. Let's start by copying that import statement from our other views.py file, then creating our first functional view:

from django.shortcuts import render

def home_view(request):
return render(request, 'home.html')

We've defined a view called home_view that renders a template called home.html. We haven't created that template yet, but don't worry - we'll get there in a minute. First, let's open up the urls.py file in our core directory and see what's inside. This file was automatically generated, so we've got some boilerplate code waiting for us:

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
# Examples:
# url(r'^$', 'djangonote.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),

url(r'^admin/', include(admin.site.urls)),
)

The patterns we define here tell our application what views to call in response to what URLs. As you can see, we start off with a single pattern already defined - appending /admin to our root url will take us to the built-in Django administrative panel. We won't be using the built-in admin at all, so let's just get rid of this. You can also see in the generated example text that it's suggested 'djangonote.views.home' be used in the pattern for our home view, but the practice of calling a view through a string like this is deprecated (I think). We'll do it a different way. We want two patterns: one that uses our home_view to display our homepage, and one that directs requests over to our notes app. We'll update our core urls.py file to look like this - note the extra import:

from django.conf.urls import patterns, include, url
from django.contrib import admin
from djangonote.views import home_view

urlpatterns = patterns('',
url(r'^$', home_view, name='home'),
url(r'^notes/', include('notes.urls', namespace='notes')),
)

Now requests to our root url (mysite.com) will be handled by our home_view, and requests to mysite.com/notes will be directed over to the urls.py file in our notes directory to handle things. We also named our homepage's url pattern and gave urls for our notes app their own namespace, which will be hugely convenient later.

The only thing we need now is to create that home.html template in our templates directory. touch templates/home.html if you haven't already, then open up the blank file in your editor. While you're at it, touch templates/base.html and open that one up, too. Let's talk about templates and inheritance for a second, because it makes your life one heck of a lot easier.

Your standard HTML webpage has a certain amount of necessary boilerplate that gets very repetitive, very quickly. The smart way to do things is to define a base template, then extend that template for your page-specific templates. It may sound a little weird to save time by writing more templates, but the benefits become apparent very quickly.

Let's start with our base.html template. To the extent we're able, we're going to stick all of the boring, repetitive stuff in here. That includes static assets like css files, and that means we'll need to gather a few things before getting started. We're going to store most of this stuff locally and deploy it later using collectstatic. Later, when your website has many users and needs to worry more about being optimized, you can think about moving these kinds of assets off onto a separate fileserver or CDN. For now, you'll want to download the .css and .js files from GetBootstrap.com for both Bootstrap (compiled and minified CSS and JS here) and the base theme (here). Collect these files into your djangonote/static/djangonote directory - drop the .css files directly into that directory, collect your .js assets in a directory called js and your font assets in a directory called fonts. For performance, you can use the minified versions of these files when available. Your djangonote/static/djangonote directory should now include:


  1. bootstrap-theme.min.css

  2. bootstrap-theme.css.map

  3. bootstrap.min.css

  4. bootstrap.css.map

  5. theme.css

  6. fonts, a directory containing four glyphicon files

  7. js, a directory containing bootstrap.min.js

Now that we've got all of the staticfiles we need, we can sensibly refer to them in our base.html file. Django has a robust templating engine that allows us to use various template tags and filters to do lots of cool things. One of the coolest is to define "blocks" of content in our templates that we can extend in other templates - this makes more sense in action. Let's open up our base.html file and get started with the <head> content.

{% load staticfiles %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-COMPATIBLE" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}DjangoNote - The Best Note{% endblock title %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{% static 'djangonote/bootstrap.min.css' %}" />
<link rel="stylesheet" href="{% static 'djangonote/bootstrap-theme.min.css' %}" />
<link rel="stylesheet" href="{% static 'djangonote/theme.css' %}" />
{% endblock stylesheets %}
</head>

Isn't it nice that we won't have to type all of that stuff again? Check out the block tags - we included one for both title and stylesheets. This will let us extend or otherwise change these blocks in other templates without duplicating all the other cruft. For instance, let's say we've written a template that extends our base.html, but we want to use a different stylesheet than the ones we're using in the base. No sweat - we can just overwrite our stylesheet block like so:

{% extends "base.html %}
{% block stylesheets %}
<link rel="stylesheet" href="{% static 'djangonote/new.css' %}" />
{% endblock stylesheets %}

Now instead of using the four stylesheets in our original base.html, this template just uses one - new.css. We could overwrite the title block in the same way. What if we wanted to add that stylesheet to the original four instead of totally replacing them?

{% extends "base.html %}
{% block stylesheets %}
{{block.super}}
<link rel="stylesheet" href="{% static 'djangonote/new.css' %}" />
{% endblock stylesheets %}

The {{block.super}} means that the code in this block is added to the block it extends. Powerful stuff! Okay, back to base.html. Just beneath the closing </head> tag, we'll define another block:

{% block scriptload %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="{% static 'djangonote/js/bootstrap.min.js' %}"></script>
<script src="{% static 'djangonote/js/docs.min.js' %}"></script>
{% endblock scriptload %}

Now we've loaded our javascript assets, and grabbed jquery from Google's servers - thanks Google!

Let's use some of the css provided by Bootstrap to create a simple navbar that'll stick to the top of our page. Later, if we want to make changes to the navbar we'll only have to change it once - in the base.html template. Since our other pages will inherit from this template, the changes to those pages will be automatic!

<body role="document">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'home' %}">Home</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">Notes</a>
</li>
</ul>
</div>
</div>
</div>

Now we have a navbar with a persistent link to our front page, and a place to add a link into our "notes" app later. We'll point this at the index urlconf in the notes app once we create and name it. Notice how the "home" link makes use of our named URL and a Django template tag.

Django includes a "messaging" framework that allows you to attach verbose messages to the request object. This is very useful - for example, consider what might happen after you create a note. It'd be nice to be directed to a page with the note on it and have a "success" message pop up letting us know everything went well, but the view that you'll use to create a note is different from the view you'll use to display a note. How do you carry the success message generated by the creation view over to the display view so we can see it? The messaging framework is a great way to handle this, and it's a good idea to include display logic for the messages we might generate in our base template. That way messages can be displayed on any template that inherits from our base.

{% if messages %}
<div class="container">
<ul class="messages">
{% for message in messages %}
<li id="message_{{ forloop.counter }}"
{% if message.tags %} class="alert alert-{{message.tags}}"{% endif %}>
{{message}}
</li>
{% endfor %}
</ul>
</div>
{% endif %}

We've introduced our first {% if %} block and our first template-based for loop. This iterates through all of the messages sent through the messaging framework, creates a list item for each message, and tries its best to color it properly (the message tags almost line up with Bootstrap's alert classes; messages.INFO matches class="alert alert-info" for instance). Easy!

Now let's define the main content area for our template:

<div class="container theme-showcase" role="main">
{% block body %}
{% endblock body %}
</div>

We don't actually put any content into the body block, because we're just going to extend it in child templates. We did wrap it in a <div> for convenience, though.

Let's throw a footer on here and finish things up:

<hr>
{% block footer %}
<div class="container marketing">
<footer>
<p class="pull-right"><a href="#">Back to Top</a></p>
</footer>
</div>
{% endblock footer %}
</body>
</html>

Phew - now we've got a base template that we can extend to easily make other pages. Let's make that home.html we wanted! Open it up and get started:

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

{% block body %}
<div class="jumbotron">
<h1 align='center'>DjangoNote!</h1>
</div>
{% endblock body %}

Thanks to our extensible base.html template, this is as easy as can be! Save your new home.html and sit back for a moment. We've made a lot of progress over the first three stages of the tutorial - let's take a moment to appreciate what we've made. Run python manage.py runserver to start the built-in development server, and point your favorite web browser at 127.0.0.1. You should see the homepage you just created - DjangoNote lives! If you don't, don't despair. Since you have DEBUG=True in your settings.py, you'll get a helpful error message anytime something goes awry.

We're making great progress! The next step will be to create a way for us to add notes into our database - we'll tackle that in part 4!

Continue to Part 4


<< back to blog index