
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!
SetupIf you haven't follow earlier tutorials, you can clone my repository from github:
mkdir -p DjangoTutorial/{static,virtualenv,source,database,media}
virtualenv --python=python3 DjangoTutorial/virtualenv/
git clone <a href="https://github.com/fozodavid/DjangoTutorial.git">https://github.com/fozodavid/DjangoTutorial.git</a> branch exercise2 single-branch DjangoTutorial/source
cd DjangoTutorial/source
touch MyTutorial/local_settings.py
***MyTutorial/local_settings.py***
import os
from MyTutorial.settings import BASE_DIR
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'rf@7y-$2a41o+4&z$ki0&=z)(ao=@+$fseu1f3*f=25b6xtnx$'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR,'..','database','db.sqlite3'),
}
}
*** end of MyTutorial/local_settings.py ***
git branch -m exercise2 master
TutorialAs 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 testOkay, 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