Django Views

This post is part of my Django series. You can see an overview of the series along with instruction on how to get all the source code here.

This article assumes you are comfortable creating a Django project, can create apps and register them into the INSTALLED_APPS list of the settings file. If not please read my Django HelloWorld article.

This article assumes you have a project called DjangoSandBox and an application called viewsintroduction.

Django Views provide out of the box functionality for displaying and CRUD ( Create, Update, Delete) of a model.

The Models

First we need a couple models to work upon. We have a simple model of a Contact and Address which are joined with a one to one relationship.

#formsintroduction/models.py
from django.db import models
from django.db.models import Model
from django.core.urlresolvers import reverse

OPTION_SEX_MALE = 'M'
OPTION_SEX_FEMALE = 'F'
OPTION_SEX_MALE_DESCRIPTION = 'MALE'
OPTION_SEX_FEMALE_DESCRIPTION = 'FEMALE'

OPTIONS_SEX = (
    (OPTION_SEX_MALE, OPTION_SEX_MALE_DESCRIPTION),
    (OPTION_SEX_FEMALE, OPTION_SEX_FEMALE_DESCRIPTION)
)


class PhoneAddress(Model):
    number = models.IntegerField()
    street_name = models.CharField(max_length=20)
    city = models.CharField(max_length=20)

    def __str__(self):
        return "{0} {1} {2}".format(self.number, self.street_name, self.city)

    def get_absolute_url(self):
        return reverse('viewsintroduction:address', args=[self.id])


class PhoneContact(Model):
    name = models.CharField(max_length=20, unique=True)
    surname = models.CharField(max_length=20, unique=True)

    height = models.FloatField()
    date_of_birth = models.DateField()
    is_family = models.BooleanField(default=False)
    sex = models.CharField(max_length=1, choices=OPTIONS_SEX)

    address = models.OneToOneField(PhoneAddress)

    def __str__(self):
        return "{0} {1}".format(self.name, self.surname)

    def get_absolute_url(self):
        return reverse('viewsintroduction:contact', args=[self.id])

Create View

The CreateView will provide us with the UI for collecting all the information for a model, the validation as defined within the model and also the ability to create a model from the post data.

#views.py
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.views.generic.edit import CreateView

from .models import PhoneAddress

class PhoneAddressCreateView(CreateView):
    model = PhoneAddress
    fields = ["city", "street_name", "number"]
    template_name = "viewsintroduction/phoneaddresscreate.html"

    def form_valid(self, form):
        messages.success(self.request, "Address created successfully")
        return super(PhoneAddressCreateView, self).form_valid(form)

    def form_invalid(self, form):
        messages.error(self.request, "Address has not been created")
        return super(PhoneAddressCreateView, self).form_invalid(form)
  • The model property registers the model class to the view
  • The fields property is a list of model field names which will be displayed, collected and populated.
  • The template_name property is the template to be used
  • The form_valid and form_invalid are not mandatory but provide us with a hook to place code upon valid and invalid validation. We use these to add a success or failure message to the Django message system. We will display these in a common base template later on.

Next we need a template which renders our form.

<!-- viewsintroduction/templates/phoneaddresscreate/base.html -->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Create Address</h2>
{% endblock title %}

{% block content %}
    <form method="post" action="{% url "viewsintroduction:address_create" %}">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        <p><input type="submit" value="Create"/></p>
    </form>
{% endblock content %}
  • The csrf_token is a cross site forgery token; we should always include this.
  • The form.as_table renders our auto generated Django form object as html.

Finally we will create our base view which will display our messages:

<!-- viewsintroduction/templates/viewsintroduction/base.html -->
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h1>Views Introduction</h1>
{% block title %}
{% endblock %}

{% if messages %}
    <ul class="messages">
        {% for message in messages %}
            <li id="message_{{ forloop.counter }}"
                    {% if message.tags %} class="{{ message.tags }}"{% endif %}>
                {{ message }}
            </li>
        {% endfor %}
    </ul>
{% endif %}

{% block content %}
{% endblock %}
</body>
</html>
  • The messages template variable is automatically available as we used the Django messaging middle ware. The app ‘django.contrib.messages’ is registered automatically by default in the INSTALLED_APPS of the settings file; it should work out of the box.

Hook in our view into out URL routing config:

#viewsintroduction/urls.py
url(r'^address/create/$', PhoneAddressCreateView.as_view(), name="address_create"),

Our project’s urls.py should register the URL prefix of viewsintroduction to our app’s url.py:

#DjangoSandBox/url.py
from django.conf.urls import patterns, include, url

from viewsintroduction import urls as viewsintroduction_urls

urlpatterns = 
    patterns('',
             url(r'^viewsintroduction/', include(viewsintroduction_urls, namespace="viewsintroduction")),
           )

And I kid you not, we are done! The ability to create an Address record including all validation!

To test run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/address/create/

Update View

The update view follows the same pattern though we inherit the UpdateView.

#views.url
from django.views.generic.edit import UpdateView

from .models import PhoneAddress

class PhoneAddressUpdateView(UpdateView):
    model = PhoneAddress
    fields = ["city", "street_name", "number"]
    template_name = "viewsintroduction/phoneaddressupdate.html"

    def form_valid(self, form):
        messages.success(self.request, "Address updated successfully")
        return super(PhoneAddressUpdateView, self).form_valid(form)

    def form_invalid(self, form):
        messages.error(self.request, "Address has not been updated")
        return super(PhoneAddressUpdateView, self).form_invalid(form)

A simple template using the as_table functionality of the Django Form to render itself:

<!--templates/viewsintroduction/phoneaddressupdate.html -->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Address Update</h2>
{% endblock title %}

{% block content %}
    <form method="post" action="">
        {% csrf_token %}
        <table>     {{ form.as_table }}   </table>
        <p><input type="submit" value="Update"/></p>
    </form>
{% endblock content %}

The only real difference from our previous CreateView example is that we need to have an identifier for the record we are updating. Here we parse the URL to extract the primary key value which should be passed into the URL:

/address/1/update
#urls.py

url(r'^address/(?P<pk>[0-9]+)/update/$', PhoneAddressUpdateView.as_view(), name="address_update"),
  • (?Pregex) means we are matching a section which we can reference later on by the word name. In our case the name is pk and the regex match is [0-9]+
  • [0-9]+ matches any number of times the digits 0 to 9.

To test this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/address/1/update/

Note: Replace 1 with your required id.

Note; there is no choice in the name of the pk parameter here as the Django code we are using requires it to be explicitly called pk.

Delete View

The delete view implementation is very similar to the update, the main difference is that we inherit form the DeleteView.

#views.url
from django.views.generic.edit import DeleteView

from .models import PhoneAddress

class PhoneAddressDeleteView(DeleteView):
    model = PhoneAddress
    template_name = "viewsintroduction/phoneaddressdelete.html"

    def get_success_url(self):
        return reverse("viewsintroduction:address_list")

    def delete(self, request, *args, **kwargs):
        messages.success(self.request, "Address deleted successfully")
        return super().delete(request, *args, **kwargs)

Our template view:

<!-- templates/viewsintroduction/phoneaddressdelete.html -->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Delete Address</h2>
{% endblock title %}

{% block content %}
    <form method="post" action="">
        {% csrf_token %}
        <table>     {{ form.as_table }}   </table>
        <p><input type="submit" value="Delete"/></p>
    </form>
{% endblock content %}

Our URL routing config:

#urls.py
   url(r'^address/(?P<pk>[0-9]+)/delete/$', PhoneAddressDeleteView.as_view(), name="address_delete"),

To test this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/address/2/delete/

Note: Replace 1 with your required id.

Detail View

The DetailView allows us to display a record from if the URL contains the primary key.

#views.py
from django.views.generic import DetailView

from .models import PhoneAddress

class PhoneAddressDetailView(DetailView):
    model = PhoneAddress
    fields = ["city", "street_name", "number"]
    template_name = "viewsintroduction/phoneaddress.html"
  • The model property contains our model class
  • The fields property contains a list of model field names as string
  • The template_name property contains the template identifier

The main difference here is that there is no auto functionality to render the record within a read-only table. We have to manually render the template details ourself.

The record is added into the template context data variable named object.

<!--template/viewsintroduction/phoneaddress.html-->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Address Details</h2>
{% endblock title %}

{% block content %}
    <table>
        <tr>
            <td>No.</td>
            <td>{{ object.number }}</td>
        <tr>
            <td>Street</td>
            <td>{{ object.street_name }}</td>
        <tr>
            <td>City</td>
            <td>{{ object.city }}</td>
        </tr>
    </table>

    <table>
        <tr>
            <td><a href="{% url "viewsintroduction:address_list" %}">Back</a></td>
            <td><a href="{% url "viewsintroduction:address_update" object.id %}">Update</a></td>
            <td><a href="{% url "viewsintroduction:address_delete" object.id %}">Delete</a></td>
        </tr>
    </table>
{% endblock content %}

We have added links to update and delete the record via the URL template operator and the URL names with their namespace such as:

{% url "viewsintroduction:address_update",  object.id %}

Hook in our URL routing config:

#urls.py

url(r'^address/(?P<pk>[0-9]+)/$', PhoneAddressDetailView.as_view(), name="address"),

To test this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/address/1/

Note: Replace 1 with your required id.

List View

The ListView class allows us to retrieve and display a list of objects:

#views.py

from django.views.generic.list import ListView

from .models import PhoneAddress

class PhoneAddressListView(ListView):
    model = PhoneAddress
    template_name = 'viewsintroduction/phoneaddresslist.html'
    paginate_by = 5

    def get_queryset(self):
        return PhoneAddress.objects.all().order_by("city", "street_name", "number")
  • The model is the name of the model class we will be displaying
  • The template_name is the name of the template we will be using to render the data
  • The paginate_by is the number of records in each page; this is optional
  • The get_queryset is a function used to return the data. Here we are returning all objects ordered by city then street_name and then number.

By default the records are contained in a list under the template variable. named object_list .

Paing is set up using the following tempalte variables;

  • is_paginated set to true or false if we have paging defined on the view
  • page_obj is a class with various properties for working with paging
  • page_obj.has_previous returns true or false if we have a previous page; i.e we are not on the first page
  • page_obj.previous_page_number returns the number of the previous page
  • page_obj.number returns the current page number
  • page_obj.paginator.num_pages returns the total number of pages
  • page_obj.has_next returns true or false depending upon if we have following pages; i.e. we are not on the last page
  • page_obj.next_page_number returns the number of the next page

We then construct the URL with the page number as a get query string variable; i.e. it ends with ?page=x where x is the number of the page we want to view.

<!-- templates/viewsintroduction/base.html -->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Address List</h2>
{% endblock title %}

{% block content %}
    <table>
        <tr>
            <th>No.</th>
            <th>Street Name</th>
            <th>City</th>
            <th></th>
            <th></th>
            <th></th>
        </tr>
        {% if not object_list %}
        <tr><td colspan="6">There are no addresses</td></tr>
        {% endif %}
        {% for address in object_list %}
            <tr>
                <td>{{ address.number }}</td>
                <td>{{ address.street_name }}</td>
                <td>{{ address.city }}</td>
                <td><a href="{% url "viewsintroduction:address" address.id %}">View</a></td>
                <td><a href="{% url "viewsintroduction:address_update" address.id %}">Update</a></td>
                <td><a href="{% url "viewsintroduction:address_delete" address.id %}">Delete</a></td>
            </tr>
        {% endfor %}
    </table>
    {% if is_paginated %}
        <div class="pagination">
            <span>
                {% if page_obj.has_previous %}
                    <a href="{% url "viewsintroduction:address_list" %}?page={{ page_obj.previous_page_number }}">Previous</a>
                {% endif %}
                <span>
                    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
                </span>
                {% if page_obj.has_next %}
                    <a href="{% url "viewsintroduction:address_list" %}?page={{ page_obj.next_page_number }}">Next</a>
                {% endif %}
            </span>
        </div>
    {% endif %}

    <span><a href="{% url "viewsintroduction:address_create" %}">Create</a></span>
    <span><a href="{% url "viewsintroduction:contact_list" %}">Contacts</a></span>
{% endblock %}

Hook in the view into the URL routing config:

#urls.py
url(r'^address/list$', PhoneAddressListView.as_view(), name="address_list"),

To view this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/address/list/

Contact Views

Please see the associated source code for how to hook in the parent Contact model views. They are basically the same as the address model. The only cool point to note is that we get a UI drop down of addresses pre-populated which allow us to set the associated joined address record.

Redirect View

The redirect view allows us to perform a server redirect via the URL routing config:

#urls.py

from django.views.generic.base import RedirectView

url(r'^redirect/$', RedirectView.as_view(url='http://djangoproject.com'), name='redirect_view'),

To view this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/redirect/

Template View

The template view allows us to display static data:

#urls.py

from django.views.generic.base import TemplateView

url(r'^about/$', TemplateView.as_view(template_name="viewsintroduction/about.html"), name="template_view")

A template would then look like this:

<!-- templats/viewsintroduction/about.html-->
{% extends "viewsintroduction/base.html" %}

{% block title %}
    <h2>Template View</h2>
{% endblock title %}

{% block content %}
    Some static information would be here!
{% endblock content %}

To view this page run the development server and navigate to:

http://127.0.0.1:8000/viewsintroduction/about/
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s