Django Series

Lesson 1: Start

Lesson 2: Git

Lesson 3: MVC

Lesson 4: Forms

Lesson 5: Models

Appendix i: Setup

Appendix ii: Shell

Appendix iii: TDD

Django's models, views and templates

MVC overview

Django loosely follows the MVC design pattern. That stands for Model-View-Controller. Model is the database handling layer defined in models.py, View is the display layer (html files), that is defined in the “templates” directory and also views.py doing this. The Controller is responsible for the user's input, surprisingly that work is also done in the views.py file. You will see these parts in action all working together.

 

 

In todays tutorial we will display an article on our website. Let's dive in!

 

If you haven't follow earlier tutorials, click here and clone branch exercise2.

 

As with the previous tutorial, we will follow git and TDD best practices. So let's create a new branch for the new feature we will implement. Let's call that article branch:

git checkout -b article

git branch

 

You should see two branches and “article” selected:

* article

master

 

 Let's activate virtualenv.

source ../virtualevn/bin/activate

 

Let's create our homepage. Before anything else, the principle of TDD require us to write so some tests. We will test if our root domain (e.g.: example.com) will return our index.html template. Our bet if it starts with <!doctype html> and there is “Hello World” in it, then it's a good enough test to determine if its our index.html. So let's insert these snippets on top and to the bottom of main/tests.py.

***main/tests.py***

from django.http import HttpRequest

...

def test_root_loads_index_html(self):

    request = HttpRequest()

    response = home(request)

    self.assertTrue(response.content.startswith(b'<!doctype html>\n<html>\n<head>'))

    self.assertIn("Hello World",response.content.decode())

 

As a side note I tell you, that I had a lot of headache to wrap my mind around the fact that writing a test a lot of times is more difficult than writing the actual thing (at least at the beginning). So you don't need to understand exactly what's going on in the tests. Focus on the “real” code just now. You will get the hang of it with time.

 

Let's run the test. It should fail as expected.
python3 manage.py test

 

Okay, so let's create index.html. Django reads html files from directories called templates in it's apps. Let's do this.

mkdir main/templates

touch main/templates/index.html

 

Let's write some minimal html.

<!doctype html>

<html>

<head>

    <meta http-equiv="Content-type" content="text/html; charset=utf=8">

    <meta name="viewport" content="width=device-width, initial-scale=1">

</head>

<body>

    <p>Hello World</p>

</body>

</html>

 

You should have a look at how the website looks like for real.

python3 manage.py runserver

 

That will start our server. Type “localhost” as a domain name (website name).

You should see:

Hello World

 

Let's run the test again. Ctrl+C to stop the server.

python3 manage.py test

 

It should pass now.

 

..

------------------------------------------------

Ran 2 tests in 0.016s

 

OK

 

We have some working code on our hand, it's time for a commit.

git status

 

You should see these differences:

...

modified: main/tests.py

modified: main/views.py

...

Untracked files:

(use "git add <file>..." to include in what will be committed)

 

main/templates/

...

 

Let's finish the procedure:

git add .

Git commit -m “Index.html created, views.py updated”

 

It's time for adding some dynamic content into our homepage. Before we dive in some word about models. Model are the equivalent of database tables in a SQL database. Django have a thing called ORM (Object Relational Mapping), which translates your SQL into Python classes and functions. Let's see a basic example.

***main/models.py***

 

Class Article(model.Models):

    title = models.CharField(max_length=100)

    text = models.TextField()

 

Will create the following SQL query:

CREATE TABLE "main_article" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT NULL, "text" text NOT NULL);

 

As usual, before anything we do, let's write some tests first. Insert these snippets onto the top and to the bottom.

***main/tests.py***

 

from main.models import Article

...

    def test_able_to_save_entries_to_db(self):

        Article.objects.create(title="My Awesome Article", text="This is a text.")

        a = Article.objects.filter(title="My Awesome Article")

        self.assertEqual(a.text,"This is a text.")

 

Run test, expected failure:

 

ImportError: cannot import name 'Article'

 

So, it's time to create our actual model.

***main/models.py ***

from django.db import models

 

# Create your models here.

class Article(models.Model):

    title = models.CharField(max_length=100)

    body = models.TextField()

 

Run test:

django.db.utils.OperationalError: no such table: main_article

 

As we see earlier, the contents of models.py will translate into an SQL query, but first we need to execute that query.

 

So let me introduce you to a new concept called migrations. Migrations are a sort of version control for your database. If you do some changes to your models in models.py, you will also need “make migrations”, these are actual files in the main/migrations folder describing the changes you made. You also need to execute these files. When you execute with the migrate command, the changes will take effect in your database. Usually, we run these two commands together.

 

python3 manage.py makemigrations

python3 manage.py migrate

 

So now, the test should pass, as indeed there is a table in the database called main_article.

 

python3 manage.py test

 

Should see:

...

---------------------------------------------

Ran 3 tests in 0.016s

 

OK

 

Test passes, time for commit.

 

git status

 

Output:

...

modified: main/models.py

modified: main/tests.py

...

 

Check your work with the git diff command. Also, you should not see any migration files over there. We took care about that , in the .gitignore file. If you see them just leave it for now and download my repo from github for the next exercise. Gitignore need to be configured properly at the beginning of a project, you cannot change it afterwards.

 

git diff

 

Everything fine, let's commit.

 

git add .

git commit -m “Article model defined.”

 

Now let's display our model content in our site. That will require some more complex tests and also some serious reorganization. As you can see I created a different class called ModelTest and moved able_to_save_entries_to_db over there. Also I implemented a setUp and tearDown function, which acts as the name implies. Before any tests the setup will set the initial environment and the tearDown will destroy that environment, in between the tests of that class will run. I invite you to study it and let me know in the comment section if I should make some points more clear here.

 

***main/tests.py***

from django.core.urlresolvers import resolve

from django.test import TestCase

from django.http import HttpRequest

from main.views import home

from main.models import Article

 

# Create your tests here.

class HomepageTest(TestCase):

 

    def test_root_resolves_to_home(self):

        root=resolve('/')

        self.assertEqual(root.func,home)

 

    def test_root_loads_index_html(self):

        request = HttpRequest()

        response = home(request)

        self.assertTrue(response.content.startswith(b'<!doctype html>\n<html>\n<head>'))

        self.assertIn("Hello World",response.content.decode())

 

 

class ModelTest(TestCase):

 

    def setUp(self):

        self.test_title = "TEST My Awesome Article TEST"

        self.test_body = "This is a text"

        Article.objects.create(

            title=self.test_title,

            text=self.test_body)

        self.article = Article.objects.all().filter(

            title=self.test_title)

 

    def tearDown(self):

        self.article.delete()

 

    def test_able_to_save_entries_to_db(self):

        self.assertEqual(self.article.values()[0]['text'],self.test_body)

 

    def test_index_html_displays_article(self):

        request = HttpRequest()

        response = home(request)

        self.assertTrue(

            response.content.startswith(

                b'<!doctype html>\n<html>\n<head>'))

        self.assertIn(self.test_body,response.content.decode())

 

Let's run our test. Expected failure. Our article is not displayed. The test is looking for the article test, so lets write that in.

 

***index.html***

...

<body>

    <p>Hello World</p>

    <p>{{ article.text }}</p>

</body>

 

What you've seen here is the Django Template Language in action. The dynamic content will be displayed in these “holes” marked with curly braces.

 

Run the test.

python3 manage.py test

Still failing. Article is nowhere to be around. The reason is because, you need to connect the data with the templates somehow. The solution lies in rewriting views.py and the home function specifically.

 

***main/views.py***

from django.shortcuts import render

from main.models import Article

 

# Create your views here.

def home(request):

    article = Article.objects.last()

    return render(request,'index.html',{'article':article,})

 

Okay. What happened here, is that we imported the Article class from our main.models. We get the last object of the article class and then render it. The third argument of render is a dictionary that will set the variables for our index.html template. We have an article variable in the curly braces already, views.py will give the meaning of this article variable.

Also just for fun, let's create an article for ourselves. Django has a built in shell interface. It is very useful for testing and exploring.

 

python3 manage.py shell

>>>from main.models import Article

>>>Article.objects.create(title=”Main Title”, text=”Wonderful new site”)

>>>exit()

 

Have a look at the site right now:

python3 manage.py runserver

Type “localhost” into the browser.

You should see:

Hello World

Wonderful new site

 

Also let's run the test.

….

----------------

Ran 4 tests in 0.016s

 

OK

 

Finally everything is passing. Let's do a commit.

 

git status

git add .

Git commit -m “Article is displayed on home page”

 

We did everything we set out to do and the code is stable. Let's merge these advancements into the master branch.

git log

 

You should see 5 commits. If you remember, we have only the top tree on our branch.

git rebase -i HEAD~3

 

Interactive menu pops up. Change the bottom two from “pick” to “squash”.

 

pick ff96ece index.html created, views.py updated

squash fd4c544 Article model defined

squash 278150b Article is displayed on home page

 

Save and quit: Ctrl+O, Ctrl+X if you are using nano. Another interactive menu pops up. You might edit the commit message the following way:

 

# This is a combination of 3 commits.

# The first commit's message is:

Article model is displayed on home page

 

# Please enter the commit message for your changes. Lines starting

Ctrl+O, Ctrl+X if you are using nano.

 

Change the branch to master:

git checkout master

 

Merge our article branch and then delete it.

git merge article

git branch -d article

 

Finally, deactivate virtualenv.

deactivate

 

Woah! You made big steps towards mastering Django. You've seen how templates, views and models interact, you also did some solid test-driven development. Let me know in the comment section if you have any question.

 

See you next time!

Tags: beginner , django , TDD , tutorial , unit test , models , viewspy , modelspy , templates , MVC