17 minutes read

We learned that Django follows the Model-View-Template pattern, similar to MVC. Now, it's time to get in touch with the central part of the MVT paradigm — the view, the idea that the Django framework stands on. Let's write some views!

What is a view?

The main logic of the interaction of applications with a user is described in views. Function-based views or Class-based views are responsible for how requests are processed and what answers (HTML pages and dynamic data on them) users get from our service. In general, a view is a function that receives a request from a user and returns information in a particular form.

We'll create simple view functions to memorize them while making a simple blog application. Let's say we have an ambitious idea of forming a community for Django learners, mentors, and enthusiasts. To start, we'll need a simple site to share our experiences and problems in learning. Let's start working on it!

By default, view functions are located in the views.py file:

As you can see, the project and application for the Django blog are already created in this picture. Let's make the preparations needed for learning views.

Preparations for simple views

Do you remember what commands we need to apply to achieve the same progress? If you don't, here's a little hint for you. Indeed later, you'll be able to create projects with your eyes shut, but for now, don't hesitate to use hints.

django-admin startproject blog
cd blog
python3 manage.py startapp blogsite

First, let's get acquainted with the render function that helps represent a simple HTML template of our blog. Frankly speaking, we don't need a template in the simplest case. For example, we can write these views in views.py file:

from django.http import HttpResponse
  
def index(request):
    return HttpResponse("Welcome to blog of Django learners!")

def list_articles(request):
    return HttpResponse("List of articles")

def contributors(request):
    return HttpResponse("Contributors of our community")

To make this HttpResponse work, add URL patterns to urls.py in the blog directory. The urlpatterns variable determines a set of request comparisons and the view functions to proceed with them.

As we have the urls.py file in the folder of the project, we'll connect it with the application's views.py file as follows with the include function:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blogsite/', include('blogsite.urls')),
]

The next step is to create a urls.py file inside the application blogsite manually, and that's where our URL addresses are to be placed:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('list_of_articles/', views.list_articles, name='list'),
    path('contributors/', views.contributors),
    ]

That's all! Now, let's run the following command:

python3 manage.py runserver

Here we see the first result of our view-making:

Nice! A bit too old-fashioned and straightforward for our goals. Of course, we can place some HTML or even big pieces of code into such view functions, but that's not useful and hard to support in case of vast projects. So let's go further and make something more exciting and informative.

By the way, we haven't yet tried to add kwargs to our view function, which is also an available option. For example, we could pass some changing data to the page of our blog:

# in the views.py file:
def contributors(request, age, name, experience):
    return HttpResponse(f"""
            <h2>Contributor of our community:</h2>
            <p>Name: {name}</p>
            <p>Age: {age}</p>
            <p>Experience and some info you'd like to share: {experience}</p>
    """)
# in the urls.py file:
urlpatterns = [
    path('contributors/', views.contributors, kwargs={"name": "Robert May", "age": 38, "experience": 
    "Two years in startup, graduated from MIT in 2020"}),
    ]

Not much can be done with this functionality, but such mechanisms can be used as a base for our coding fantasy. This is what we get as a result:

Since our blog needs a database to store the information our future community members will share, let's create models in models.py file for articles and contributors (or authors, if you like):

from django.db import models
from django.contrib.auth.models import User
from datetime import date
from django.urls import reverse


class Article(models.Model):
    title = models.CharField(max_length=250, help_text='Enter a title for a new article')
    text = models.CharField(max_length=1000, help_text='Place your story here')
    author = models.ForeignKey('Contributor', on_delete=models.SET_NULL, null=True)
    date = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.title


class Contributor(models.Model):
    name = models.CharField(max_length=250, help_text='Enter first name of the contributor')
    surname = models.CharField(max_length=500, help_text='Enter surname of the contributor')
    about = models.CharField(max_length=2000, help_text='Tell us about yourself, your experience and current work')
    email = models.EmailField(null=True, blank=True)

    class Meta:
        ordering = ['surname', 'name']

    def __str__(self):
        return f'{self.surname}, {self.name}'

Don't forget to register your models in the admin.py file:

from django.contrib import admin
from .models import Article, Contributor

admin.site.register(Article)
admin.site.register(Contributor)

Also, remember to migrate changes in the project:

python3 manage.py makemigrations
python3 manage.py migrate

Fill your database with articles using the admin interface, create a superuser through the terminal, and add objects:

We will also need a template to represent the results of data processing. Don't forget that we should add the templates/blogsite folder to the tree of the application manually:

We'll start with the index.html page, which will be the list of articles in the blog. For example, the template may look like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Greetings page</title>
</head>
<body>
  <h1>Hello, {{ greetings_to }} !</h1>
  <h1>Unread articles for today: {{num_articles }}</h1>
  {% for article in articles %}
    <h4>{{ article.title }}</h4>
    <h4>{{ article.author }}</h4>
    <p>{{ article.date }}</p>
    <p>{{ article.text }}</p>
    <be>
  {% endfor %}
</body>
</html>

One more preparation step, and we'll be ready to create a view function at last. Let's modify urlpatterns in the urls.py file:

urlpatterns = [
    path('', views.index, name='articles_list'),
    ]

Create view functions

We can now create a view function for the index page of our blog:

def index(request):
    greetings_to = 'Anonymous'
    num_articles = Article.objects.all().count()
    articles = Article.objects.all
    return render(request, 'blogsite/index.html', {'num_articles': num_articles, 'greetings_to': greetings_to, 'articles': articles})

Here we define the variables that we use in the HTML template, get data from the database (which is called a QuerySet, a list of objects of a model) and pass them as a dictionary to the render function.

The result may look like this:

It's nice to see our work's results already, right? But to make our view function look prettier, let's form a special dictionary named context which is more convenient to work with:

# views.py file
def index(request):
    num_articles = Article.objects.all().count()
    articles = Article.objects.all
    context = {
        'greetings_to': 'Anonymous',
        'num_articles': num_articles,
        'articles': articles,
    }
    return render(request, 'blogsite/index.html', context=context)

Class-based views

By now, we have a list of articles on the main page. But to extend the functionality of our application, let's add the possibility to open a page with an article we're interested in by link from the list. We need to extract information from URL patterns and pass it to the view for that purpose. For that purpose, we will use a different way of forming class-based views instead of functions.

Django framework provides us with generic views from the box, reducing the code we need to write and simplifying its maintenance (extremely helpful in complicated wide applications).

To change the existing solution, let's start with a URL pattern:

urlpatterns = [
    ...
    path('', views.ArticleListView.as_view(), name='index')
    ]

The view function's name looks different than the previous one we created. That's because we're going to implement it as a class. It's inherited from a generic view function that already exists. As you can see, we're calling the class method as_view() . Let's look at the class itself:

from django.views import generic

class ArticleListView(generic.ListView):
    model = Article
    context_object_name = 'articles'
    queryset = Article.objects.all()
    template_name = 'index.html'

The generic views query the database for instances of our model and render the existing template. If we didn't specify the template name, the view class would look for articles_list.html by default regarding the context_object_name variable. It's not necessary to define the queryset variable, but it's helpful if we need to use filters for data from the database by overriding methods of the class, like utilizing the get_queryset() method:

from django.views import generic

class ArticleListView(generic.ListView):
    model = Article
    
    def get_queryset(self):
        return Article.objects.filter(title_icontains='django')[:3]

In such case, our list will contain only the three first articles with the word django in the title.

By the way, our ArticleListView doesn't work the same way as the index function we've created. There is no greeting name on the page yet (of course, the name could be changed dynamically, but it's not the point of our topic, so it's simplified to show the principle of variables use), as well as no amount of articles available. And that's a good moment to demonstrate one more way to override context data by adding more information to the generic view. It can be done like this:

class ArticleListView(generic.ListView):
    model = Article
    context_object_name = 'articles'
    queryset = Article.objects.all()
    template_name = 'index.html'

    def get_context_data(self, **kwargs):
        num_articles = Article.objects.all().count()
        context = super(ArticleListView, self).get_context_data(**kwargs)
        context['greetings_to'] = 'Anonymous'
        context['num_articles'] = num_articles
        return context

In get_context_data() function, we call the immediate context of the class first and then create some new data we need to add to the template.

One more option we can use if needed is pagination. Imagine that our blog is popular, our community expands every minute, and the number of articles in the database increases. To control the number of articles presented on one single page, we need only one extra line in the view:

class ArticleListView(generic.ListView):
    model = Article
    paginate_by = 10

When paths are modified, manually updating every URL within your templates can be difficult. The solution to this is to define functions that return the URL instead. As we plan to have working links in the list, let's change our Article model by adding a class method get_absolute_url().

class Article(models.Model):
    ...

    def get_absolute_url(self):
        return reverse('article-detail', args=[str(self.id)])

HTML-template in the index.html also needs to be modified:

{% for article in articles %}
      <li>
        <a href="{{ article.get_absolute_url }}">{{ article.title }}</a> {{article.author}}
      </li>
{% endfor %}

The view for article representation may look minimalistic if it's inherited from generic DetailView:

class ArticleDetailView(generic.DetailView):
    model = Article
    template_name = 'article.html'

Oh, and don't forget to add HTML-template article.html to the templates/blogsite folder!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Time to learn something new!</title>
</head>
<body>
  <h3>{{article.title}}</h3>

  <h4>{{article.author}}</h4>
  <br>
  <h4>{{article.text}}</h4>
</body>
</html>

The only thing missing is the path for this page:

urlpatterns = [
    ....
    path('article/<int:pk>', views.ArticleDetailView.as_view(), name='article-detail'),
    ]

The primary key of a model instance accesses the article we want to read. And we didn't need any additional code lines to make it work. It's all done by DetailView and get_absolute_url() method.

Conclusion

Well, the skeleton of the blog for the Django community is created and working. Great job! Of course, it can be expanded by authorization functionality, login and logout views, forms for adding new articles, CSS styles, and others. So much to do!

Let's summarize what we've learned:

  • what is a view and how to process simple requests;

  • how to create a view function and its parameters and methods;

  • got in touch with the class-based views: generic ListView and DetailView, methods overriding, and pagination.

8 learners liked this piece of theory. 9 didn't like it. What about you?
Report a typo