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

Git Workflow with Django

Git is the most popular version control software right now, and will be for a long time. The git workflow is branch based. What that means, is that you can experimenting with ideas, by open up a new branch and you can easily discard the changes, if you aren't satisfied with it. On the other hand, if your branch proves to be successful you can “merge” it into the “master”. This structure lets you control the versions of your program to a great extent. Combine this methodology with unit testing and you can make no mistake.

 

Let's dive in! If you haven't done the earlier tutorial I have “How to start a Django application”, you can clone my repository from github:

mkdir -p DjangoTutorial/{static,virtualenv,source,database,media}

virtualenv --python=python3 DjangoTutorial/virtualenv/

git clone https://github.com/fozodavid/DjangoTutorial.git --branch exercise1 --single-branch DjangoTutorial/source

cd DjangoTutorial/source

touch MyTutorial/local_settings.py

 

***MyTutorial/local_settings.py***

import os

from MyTutorial.settings import BASE_DIR

SECRET_KEY = 'rf@7y-$2a41o+4&z$ki0&=z)(ao=@+$fseu1f3*f=25b6xtnx$'

DEBUG = True

ALLOWED_HOSTS = []

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 exercise1 master

source ../virtualenv/bin/activate

pip install -r requirements.txt

If you come from earlier tutorial, just cd into the source directory and put into the terminal:

git checkout -b app

git branch

Should see this:

* app

master

That creates a new branch and you make changes without affecting the master. Activate virtualenv (if you haven't already) and let's create a new application then:

source ../virtualenv/bin/activate

python3 manage.py startapp main

ls

Should see:

main manage.py MyTutorial

Our apps have their own directory in Django. In order for django to recognize them, you need to put them into the INSTALLED_APPS in MyTutorial/settings.py

***MyTutorial/settings.py***

...

INSTALLED_APPS = [

    'main',

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

]

...

Now, we initiate our testing capabilities. Remember testing and version control goes hand in hand. In a collaborative effort with other developers, they will only allow you to provide code with passing unit tests.

We only do some “smoketest” which will gives you the feedback if our unit test systems are working or not.

*** main/tests.py***

from django.test import TestCase

 
 

class SmokeTest(TestCase):

    def test_wrong_results(self):

        self.assertEqual(1 + 1, 3)

 

Try it out with the following command:

python3 manage.py test

It should fail as expected:

Creating test database for alias 'default'...

F

======================================================================

FAIL: test_wrong_results (MyTutorial.tests.SmokeTest)

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

Traceback (most recent call last):

File "/home/david/Tutorial/source/MyTutorial/tests.py", line 5, in test_wrong_results

self.assertEqual(1 + 1, 3)

AssertionError: 2 != 3

 
 

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

Ran 1 test in 0.001s

 
 

FAILED (failures=1)

Destroying test database for alias 'default'...

 

Well, everything goes to plan, now it's a good time to do a git commit. Commit means that you want the changes you made to be saved.

First let's see what you have done so far with git status:

git status

Should see:

On branch app

Changes not staged for commit:

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

(use "git checkout -- <file>..." to discard changes in working directory)

 

modified: MyTutorial/settings.py

 

Untracked files:

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

 

main/

 

no changes added to commit (use "git add" and/or "git commit -a")

 
 

Before you can save(commit), you need to review and select the files for change. We are happy with all developments, so we 'add' all files.

git add .

When you commit, you also need to provide a “commit message”. That way, you can review later what you have done. This provides you with a nice commented history of your work.

git commit -m “main application created, smoketest expected failure”

Once we committed our changes it's time to do some real unit test. Change the main/test.py file accordingly:

***main/tests.py***

from django.core.urlresolvers import resolve

from django.test import TestCase

 
from main.views import home

 

class HomePageTest(TestCase):

    def test_root_resolves_to_home(self):

        root = resolve('/')

        self.assertEqual(root.func,home)

 

Now, it's time to run the test:

python3 manage.py test

But we have an error:

Creating test database for alias 'default'...

E

======================================================================

ERROR: main.tests (unittest.loader.ModuleImportFailure)

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

Traceback (most recent call last):

File "/usr/lib/python3.4/unittest/case.py", line 58, in testPartExecutor

yield

File "/usr/lib/python3.4/unittest/case.py", line 577, in run

testMethod()

File "/usr/lib/python3.4/unittest/loader.py", line 32, in testFailure

raise exception

ImportError: Failed to import test module: main.tests

Traceback (most recent call last):

File "/usr/lib/python3.4/unittest/loader.py", line 312, in _find_tests

module = self._get_module_from_name(name)

File "/usr/lib/python3.4/unittest/loader.py", line 290, in _get_module_from_name

__import__(name)

File "/home/david/Tutorial/source/main/tests.py", line 3, in <module>

from main.views import home

ImportError: cannot import name 'home'

 

 

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

Ran 1 test in 0.001s

 

FAILED (errors=1)

Destroying test database for alias 'default'...

 

Error means, that we couldn't even run the test, because there is some bigger bug in the software. If you read carefully the output, you see that the cause of error was “ImportError”. Could not this function named “home”. Well, this function come from main/views.py. (Don't worry if you don't understand why at this point. This is inherent to Django's MVC structure). Let's make this test pass.

***main/views.py***

from django.shortcuts import render

 
def home(request):

    pass

 
Run another test:

python3 manage.py test

Output will look like this:

Creating test database for alias 'default'...

E

======================================================================

ERROR: test_root_resolves_to_home (main.tests.HomepageTest)

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

Traceback (most recent call last):

File "/home/david/Tutorial/source/main/tests.py", line 8, in test_root_resolves_to_home

root = resolve('/')

File "/home/david/Tutorial/virtualenv/lib/python3.4/site-packages/django/urls/base.py", line 27, in resolve

return get_resolver(urlconf).resolve(path)

File "/home/david/Tutorial/virtualenv/lib/python3.4/site-packages/django/urls/resolvers.py", line 297, in resolve

raise Resolver404({'tried': tried, 'path': new_path})

django.urls.exceptions.Resolver404: {'tried': [[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]], 'path': ''}

 

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

Ran 1 test in 0.003s

 

FAILED (errors=1)

Destroying test database for alias 'default'...

 
This error is a little trickier and the output, which is called “Traceback” is more cryptic. If you carefully examine the text, we see that the problem is with the url resolver, and the bug stems from the “root=resolve('/')” line. Let's have a make some changes in the MyTutorial/urls.py file then.

***MyTutorial/urls.py***

from django.conf.urls import url

from django.contrib import admin

from main.views import home

 
 

urlpatterns = [

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

    url(r'^$', home),

]

Run test again:

Creating test database for alias 'default'...

.

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

Ran 1 test in 0.003s

 
 

OK

Destroying test database for alias 'default'...

 

Great! Our test has passed! Finally we can do a commit and wrap up this session:

git status

git diff

Should see, “url(r'^$', home),”added and also main/tests.py expanded. (If you stuck in the git diff display, press 'q' to escape.)

git add .

git commit -m “Root url resolves to empty site, unit test passes.”

Before we merge our branch back to master, we should “squash” the commits. Than means we more history we really need, and we can fuse them together. Have a look at history now:

git log

Should see 3 commits.

Now we squash commits together with the following command:

git rebase -i HEAD~2

Interactive editor comes up. Let's change the second line's beginning from 'pick' to 'squash'. Your file should look like this:

pick 3c28df2 main application created, smoketest expected failure.

squash ef536d1 Root url resolves to empty site, unit test passes

 
 

# Rebase e403697..ef536d1 onto e403697

#

# Commands:

# p, pick = use commit

# r, reword = use commit, but edit the commit message

# e, edit = use commit, but stop for amending

# s, squash = use commit, but meld into previous commit

# f, fixup = like "squash", but discard this commit's log message

# x, exec = run command (the rest of the line) using shell

#

# These lines can be re-ordered; they are executed from top to bottom.

#

# If you remove a line here THAT COMMIT WILL BE LOST.

#

# However, if you remove everything, the rebase will be aborted.

#

# Note that empty commits are commented out

If your editor is nano, then press Ctrl+O to save, then Ctrl+X to exit. Once you done that you will get a screen about the commit messages. As this is a combined commit, it should be reflected in the commit message:

# This is a combination of 2 commits.

# The first commit's message is:

 

main application created,root url resolves to empty site, unit test passes.

 

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

# with '#' will be ignored, and an empty message aborts the commit.

# rebase in progress; onto 1c9a03f

# You are currently editing a commit while rebasing branch 'app' on '1c9a03f'.

#

# Changes to be committed:

# modified: MyTutorial/settings.py

# modified: MyTutorial/urls.py

# new file: main/__init__.py

# new file: main/admin.py

# new file: main/apps.py

# new file: main/models.py

# new file: main/tests.py

# new file: main/views.py

#

Ctrl+O to save and then Ctrl+X to escape.

Git log

You should see only 2 commits.

It's time for merging back to master.

git checkout master

git merge app

Output should be similar to this (it's okay if some numbers are different)

Updating 1c9a03f..e6c9b6f

Fast-forward

MyTutorial/settings.py | 1 +

MyTutorial/urls.py | 2 ++

main/__init__.py | 0

main/admin.py | 3 +++

main/apps.py | 5 +++++

main/models.py | 3 +++

main/tests.py | 9 +++++++++

main/views.py | 5 +++++

8 files changed, 28 insertions(+)

create mode 100644 main/__init__.py

create mode 100644 main/admin.py

create mode 100644 main/apps.py

create mode 100644 main/models.py

create mode 100644 main/tests.py

create mode 100644 main/views.py

Now, you can safely delete your unneeded branch.

git branch -d app

 
 

Congratulations!

You learned the basics of git workflow and unit testing! Let me know in the comment section if you have any question.

Take care!

Tags: beginner , django , git , svc , tutorial , unit test , source control