Views and URLConfs in Django

In the previous chapters we have learned how to setup a Django project and run development server. In this chapter we will learn the basics of creating dynamic web pages in Django.

Creating your first view

Let's start simple. In this section we will create a webpage that outputs "Hello Django". To do this open views.py located in the blog app (i.e TGDB/django_project/blog) in your favourite text editor.

At this point views.py should look like this:

from django.shortcuts import render

# Create your views here.

Delete everything and enter the following code.

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello Django")

We have just creted a simple view function.

So what's a view function ?

A function whose job is to accept request and returns a proper reponse.

Let's step through the code one line at a time

1) In line 1 we are importing HttpResponse class from django.http module
2) In line 3-4 we are defining a function and returning an instance of HttpResponse object

The important thing to remember about the view function is that every view function must accept a parameter called request by convention, which is an object of type HttpRequest. Certainly you can call this parameter anything you want however it is highly recommended not to change it. The HttpRequest object contains the information about the current web request that has triggered this view.

Every view method must return an HttpResponse object. To create a simple HttpResponse object just pass a string to HttpResponse representing the content of the page.

Now we have created a simple view. To call this view you must create a url pattern in the URL Configuration or URLconf in short. You can think of URLconf as a table of contents for Django powered website. In other words URLconf is a mapping between urls and view functions that should be called for those urls. It is Django's way of saying for this url call this view function and for this url call that view function and so on.

We create url pattern using url() function. It accepts two arguments, a regular expression to match the url and name of the view function to call for this url.

Lets create a url pattern. Open urls.py located in the Django Project Configuration directory (i.e TGDB/django_project/django_project). This file is also known as sitewide urls.py. The contents of urls.py file should look like this.

from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

Note: Comments are removed from the above code snippet to save space.

To tell the existence of index() view function in urls.py you need to do two things:

1) add from blog.views import index towards the end of the import list
2) Create a new url pattern by adding the following line at the beginning of the urlpatterns list.

url(r'^$', index)

The contents of the urls.py file should now look like this:

from django.conf.urls import url
from django.contrib import admin
from blog import views

urlpatterns = [
    url(r'^$', views.index),
    url(r'^admin/', admin.site.urls),
]

Start you Django development server if not already running and visit http://127.0.0.1:8000/. If everything works you will see the following output:

The first parameter to url() is a regular expression string and the second is the name of the view function. The regular expression r'^$' matches nothing. In other words r'^$' refers to the root of the website. If our domain is http://example.com/ then a request to http://example.com/ will call index() view function. In other words when user requests http://example.com/ Django uses urlpatterns list to figure out which method to call. When it finds one url pattern that matches, it calls the view function associated with that pattern. If it doesn't find any matching pattern, a HTTP 404 error is returned.

Don't forget what Django calls view is actually a controller.

The url() function accepts many other optional parameters, one such parameter is name keyword argument. The name keyword arguments allows us to give a unique name to the the url pattern. So what's the use of defining a name ? Defining a name allows us to create urls automatically in our templates and views. We will see how it's done in the upcoming chapters. For now let's give a name to our url newly created url pattern.

urlpatterns = [
    url(r'^$', views.index, name='blog_index'),
    url(r'^admin/', admin.site.urls),
]

Django 404 errors

Our URLconf at this point contains only two url pattern: one provided by django and the one which we wrote ourself. So what would happen if you request a request a different url ? Open your browser and try visiting http://127.0.0.1:8000/django. If the requested url doesn't matches to any of the url patterns then Django throws a 404 error. As the requested url is not defined in the URLconf, Django throws a HTTP 404 not found error. Here is how it looks:

The important thing to note about this page is that it gives a way lot more information than it is required. Notice that it exactly tells what url patterns Django tried from which URLconf before throwing 404 error. Sure, this is sensitive information and should be disclosed only to the people involved in the development of the web application .

Now You might say - Okay I understand all this information is sensitive, so why Django is revealing all this in the first place.

Because by default Django project installed with DEBUG set to True. To view this setting open settings.py in Django Configuration directory located at TGDB/django_project/django_project.

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'a7#0(*xelz9ap39m7xbqftbpmftp97swna!+o1$fvy(b1-tai@'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True                                                        

ALLOWED_HOSTS = []

Notice that DEBUG is set to True.

To turn-off the debug mode set the value of DEBUG to False. When debug mode is off Django outputs a different HTTP 404 response without any sentive information. As we are currently in the development phase keep the DEBUG setting to True.

Mapping urls the right way

Currently we have only one application namely blog in our Django project. Generally a project consists of atleast 3-4 apps. If we keep writing URLconf for every app in the site-wide urls.py file soon it would become a mess. So instead of mappings the urls directly to project's urls.py, we can make our application modular by creating urls.py for every application. That way we can manage urls much more efficiently and easily. To do this first create urls.py file inside the blog app and add the following code.

from django.conf.urls import url
from blog import views

urlpatterns = [
    url(r'^$', views.index, name='blog_index'),
]

In line 1 and 2 we are importing necessary function and module. In line 4 we are creating URLconf for the blog app.

The next step is to inform the Django project about the URLconf for the blog app. To do so modify the sitewide urls.py as follows:

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

urlpatterns = [
    url(r'^blog/', include('blog.urls')),
    url(r'^admin/', admin.site.urls),
]

The include() function tells sitewide urls.py file about the existence of urls.py in the blog app. The important thing to note here is that in the regular expression r'^blog/' we don't have a trailing $ character instead we have a trailing slash /. This is done so because whenever Django encounters include() function, it chops off whatever the part of the url matches up to that point and sends the remaining part of the url to the included URLconf for further processing.

Open your browser and navigate to http://127.0.0.1:8000/blog/. Django will greet you with "Hello Django" response.

Digging Deep into the URLs

Lets see what happens when you request a page.

1) When you request a page the first thing Django does is to remove the host portion of the url. For example in the URL http://overiq.com/contact/ the host portion is overiq.com.

2) Then Django reads value of variable ROOT_URLCONF from Django project settings.py file. So what ROOT_URLCONF contains ?

ROOT_URLCONF contains the URLconf which must be loaded first. It is also known as root URLconf or sitewide URLconf. In our case it points to urls.py located in TGDB/django_project/django_project.

ROOT_URLCONF = 'django_project.urls'

3) Then Django checks each url pattern one by one in the root URLconf with the requested url until a match is found. If pattern is not found a 404 error is returned.

4) If a pattern is found then it calls the associated view function. If the second parameter to the url() function includes a call to include() function then Django chops off whatever the part of the url matches up to that point and sends the remaining part of the url to the included URLconf for further processing.

Conside the following example:

Lets say the requested url is http://example.com/time/, the first thing Django does is to remove the host portion of the url. After stripping host portion http://example.com/time/ becomes /time/. The string /time/ is then checked against each url pattern one by one, until a match is found. If a match is found corresponding view function is called. Otherwise a 404 error is returned.

Outputting Dynamic Data

The view function we created in the above section was very simple just outputting "Hello Django", notheless it introduced you to some basics concepts of how Django processes the urls behind the scene. In the next view we will create something more dynamic. Let's create a simple web page to output current date and time. If you have done some Python programming then you may already know that Python has datetime module for working with date and time. Here is how to use it:

>>> import datetime
>>> current_datetime = datetime.datetime.now()
>>> current_datetime
datetime.datetime(2017, 1, 24, 13, 59, 42, 135163)
>>> print(current_datetime)
2017-01-24 13:59:42.135163
>>>

It is important to note that above the snippet is pure Python and it has nothing to do with Django. To return current date and time we first have to create a new view. We will create this view inside our blog app. Open views.py in the blog app and add a new view function called today_is() below index() view function as follows:

import datetime
from django.http import HttpResponse

...

def today_is(request):
    now = datetime.datetime.now()
    html = "<html><body>Current date and time: {0}</body></html>".format(now)
    return HttpResponse(html)

Let's step through the changes we have made to the views.py file

1) In line 1, we have added an import statement to import datetime module, so that we can calculate current date and time.

2) In line 7, we have defined today_is() function. In line 8 we are calculating current current date and time by calling now() method and assigning the result to the now variable. In line 9 we are creating an html response using string object's format() method. The {0} inside the string is just a placeholder for the current date and time and will be replaced by the value of variable now. It is important to note that the variable now represents a datetime object not a regular string but when the value of now is printed inside the string in place of {0}, the __str__() method from datatime object converts datatime object to a string. Finally the view returns an HttpResponse() object containing the generated response.

With view function in place, lets create a new urlpattern to call this view. Open blog app's urls.py and add the following url pattern to call today_is() view function as follows:

from django.conf.urls import url
from blog import views

urlpatterns = [
    url(r'^time/$', views.today_is, name='todays_time'),
    url(r'^$', views.index, name='blog_index'),
]

We have added a new urlpattern to map /time/ URL to the view function today_is(). At this point you are probably getting the hang of this. Start the server if not already running and visit http://127.0.0.1:8000/blog/time/. If everything went fine Django will greet you with current date and time.

Views Limitation

So far we have been able to pull off a basic app using just using View part of the MTV pattern. But still our app is severly limited because of following reasons:

1) We are harcoding HTML code inside our views. At a later date, if we want to modify our html it would be very painful to go through each view one by one to modify the page. Django comes bundled with a powerful templating system which allows us to create complex HTML pages easily instead of hardcoding them inside views. If we keep hardcoding HTML directly in the view we wouldn't be able to use loops or conditional statements that Django templating system provides inside our html (we will see this while working with Django templates).

2) In the real world a page consists of many dynamic components. Embedding dynamic content in a large page using format() method is very error prone and tedious.

3) At this point we haven't introduced database stuff because it would create more mess.

In the upcoming chapters you will see how using Models and Templates in conjuction with Views helps us to greatly simplify all the above mentioned issues.