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

Introduction to Django Models

 

 

In this part of the tutorial, I will show you some basic model definition by creating a simple blog. As discussed earlier, database tables are translated from the models.py files.

 

If you haven't followed along with earlier tutorials, you can do the setup here. Choose branch exercise4.

 

As usual we start by creating a new branch:

git checkout -b blog

 

Activate virtualenv, if you haven't already:

source ../virtualenv/bin/activate

 

A is an application by itself, so we create one inside Django with the following command.

python3 manage.py startapp blog

 

You should see these directories and files in your source dir:

blog/

main/

manage.py

MyTutorial/

requirements.txt

 

In order for django to discover the new application, you need to include it in the installed apps in settings.py

***MyTutorial/settings.py***

...

INSTALLED_APPS = [

    'main',

    'blog',

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

]

As usual we write some tests first. If you have a look at the other test file main/tests.py you can see, that we have some very similar goal here. It might be tempting to import those tests and tweak it a little, but that wouldn't be right. As for Unit Tests, it needs to be completely independent from each other. So it is not a problem, if they look too much alike.

 

***blog/tests.py***

from django.core.urlresolvers import resolve

from django.test import TestCase

from django.http import HttpRequest

from blog.models import BlogPost

from blog.views import view_post

 

# Create your tests here.

class BlogPostTest(TestCase):

 

    def setUp(self):

        self.title = "Blog Post Test"

        self.slug = "blog-post-test"

        self.body = "This is a test."

        self.date = "2016-11-26"

 

        BlogPost.objects.create(

            title=self.title,

            slug=self.slug,

            body=self.body,

            date=self.date)

 

    def test_blogpost_resolves_to_slug(self):

        blogpost = resolve('/blog/' + self.slug + '/')

        self.assertEqual(blogpost.func,view_post)

 

    def test_blogpost_displays_article(self):

 

        request = HttpRequest()

        response = view_post(request,self.slug)

        self.assertTrue(

            response.content.startswith(

                b'<!doctype html>'))

        for i in [self.title,self.body]:

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

 

    def tearDown(self):

        BlogPost.objects.all().filter(

            title=self.title).delete()

 

So what did we do here? We created a new test class, which tests our blog application. In the setUp function, we create some title, body, slug variables, which we can refer to later. We also a create BlogPost object. In the first test we check if our custom url will trigger the right view function. I think the second tests title speaks for itself. We call the view_post function, which we will create in a moment, with our custom slug (the blog post identifier as in the URL). Then check if the response contains our chosen title, and body.

 

If you run the tests now:

python3 manage.py test

 

You should have two failures:

FF.......

 

So let's create the model classes first, because that's the most interesting.

 

***blog/models.py***

from django.db import models

from django.core.urlresolvers import reverse

from django.template.defaultfilters import slugify

 

# Create your models here.

class BlogPost(models.Model):

    title = models.CharField(max_length=255)

    slug = models.SlugField(max_length=255)

    body = models.TextField()

    date = models.DateField(auto_now_add=True)

 

    def save(self, *args, **kwargs):

        if not self.id:

            self.slug = slugify(self.title)

        super(BlogPost, self).save(*args, **kwargs)

 

    def get_absolute_url(self):

        return reverse('blog.views.view_post', args=[str(self.slug)])

 

What is happening here? So our database tables are model classes in Django. The fields are variables. So you declare a variable, and that will be the title of the database column/field. Then, you choose the appropriate field type. One if the method is saving your title as a slug (url) if it's a new post (no id yet), the other method let's you reference this post as an url.

 

Now, let's connect these models to the templates, by the views functions.

 

***blog/views.py***

from django.shortcuts import render_to_response, get_object_or_404

from blog.models import BlogPost

 

 

# Create your views here.

def view_post(request, slug):

    post = get_object_or_404(BlogPost, slug=slug)

    return render_to_response("blogpost.html", {'post' : post,})

 

Okay, so our view_post function takes in the slug and fetches the corresponding BlogPost model object. Then it renders the blogpost.html template, where the post variables refer to our BlogPost object.

 

Let's add our BlogPost slug to the urls:

***MyTutorial/urls.py***

from django.conf.urls import url

from django.contrib import admin

from main.views import home, form, subscribed, thanks

from blog.views import view_post

 

urlpatterns = [

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

    url(r'^form/',form),

    url(r'^thanks/',thanks),

    url(r'^subscribed/',subscribed),

    url(r'^$', home),

    url(r'^blog/(?P<slug>[-\w\d\_]+)/$', view_post, name='view_post'),

]

 

So now you can reference your Post as /blog/my-blog-post-title.

 

Let's create some basic template. See, how it all comes full circle!

***blog/blogpost.html***

<!doctype html>

<html>

<head>

    <title>{{ post.title }}</title>

</head>

<body>

    <h1>{{ post.title }}</h1>

    <p>{{ post.body }}</p>

    <br>

    <p>created at {{ post.date }}</p>

</body>

</html>

 

If you run the tests, you will still have no luck. Even though, every piece of code is in place. You need to update the database to reflect the changes.

python3 manage.py makemigrations

python3 manage.py migrate

 

Now, if want to make a blog post, it's not so convenient:

python3 manage.py shell

>>>from blog.models import BlogPost

>>>BlogPost.objects.create(title=”My Title”,slug=”my-title”,body=”Hello World”,date=”2016-11-29”)

>>>exit()

 

So you are able to see it, if you start up your development server:

python3 manage.py runserver

 

Write to the url line in the browser: http://localhost/blog/my-title/ . Should see this:

 

Run the tests:

python3 manage.py test

 

It should all pass.

 

Let's commit our changes and merge into the master branch.

git status

git add .

git commit -m “Added blog application, able to create basic blog posts”

 T

Change you branch back to master:

git checkout master

 

Merge the blog branch back to master and then delete the branch not needed.

git merge blog

git branch -d blog

 

So you learned how to create some basic database model in Django. It is not really user friendly yet, but soon you will see how to be the master of your Django universe.

 

Stay tuned for the next lesson, where I will show you, how to handle your models with grace in the Django Admin.

 

Until next time.

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