This article is about Django's View and URL components. When an HTTP request is ingested by CGI, WSGI (Web Server Gateway Interface), Django's URL pattern matcher routes the request to a designated view. The role of a view is to process the request and generate an appropriate response.

The role of View process the request into a response. In the process, View interacts with Model and Template typically. A view function takes a Python request object, which is parsed from the raw HTTP bytes received from WSGI. It facilitates access to data from databases, media assets, or other services, and ultimately returns an HTTP response.

URL pattern matching

There can be multiple urls.py in a project. By default, the root urls.py is located at ${project}/urls.py. This project-level urls.py dispatches requests to other app-specific urls.py files or directly to view functions that accept the request object as the first parameter.

# Example project structure:
project/
├── urls.py           # Root URL configuration
├── app_a/
│   └── urls.py       # App A's URL configuration
└── app_b/
    └── urls.py       # App B's URL configuration

Structure of a URL

The first lesson about HTTP is the URL. The URL is structured as follows:

{scheme}://{origin}}/{URI}?{query_parameters}

  • scheme: http, https, ftp, etc.
  • origin: domain name, port number, ip address
  • URI: path to the resource, e.g. "/path/to/resource"
  • query parameters: key-value pairs, and used to pass data to the server in GET requests.

By the way, GET request does not have a body. Therefore, the query string is the only way to pass data to the server. While a POST request has a body, so the query string is not necessary. For system mutating requests such as submitting object creation forms, POST is used. It is the programmer's duty to handle methods of HTTP request to different tasks.

Namespace and URL resolve

The urls.py file is responsible for mapping URL formats to corresponding views. It serves as a routing mechanism, directing incoming requests to the appropriate view based on the URI.

Creating an urls.py for each app is the best way to make Django apps reusable. Example of urls.py in an app:

# app_a/urls.py:
from django.urls import path
from . import views

app_name = 'app_a' # Namespace
urlpatterns = [
    path('view/', views.view_name, name='view_name'),
    path('another-view/', views.cbv.as_views(), name='another_view'),
    path('sub-app/', include('app_a.sub_app.urls')),
]

The path lives in django.urls.path, and a few key objects under "django.urls" are:

  • django.urls.path(), map urlPath:str to view callable. An example of a use case: routing a request to a view function
  • django.urls.reverse(), map urlname:str to relativePath:str. An example of a use case: generating a visiting URL for a model data
  • django.urls.include(), include apps' URLs patterns, with a nickname namespacing
  • django.urls.resolve(), map urlPath:str to urlname:str. It is the reverse of the "reverse" function.

Namespaces are essential for organizing URL patterns, especially when multiple apps are involved. It is used in templates to avoid naming conflicts:

<a href="{% url 'app_a:view_name' %}">Link to App A View</a>

This way, one can ensure that the correct view is referenced, even if there are other views with the same name in different apps.

FBV and CBV

There are two types of Views, function-based view FBV and class-based view CBV. Function based view is a simple function that takes a request and returns a response.

Class-based view is a class that inherits from Django's View class and has a method get and post to handle simple scenarios. To use CBV well, one may want to check CBV's callbacks (dispatch, get_context_data) which can be found in the base CBV classes. CBV is more complicated and there is little discussion of the callback order. One may better log the function call to understand the flow.

from django.http import JsonResponse

def my_view(request):
    data = {"message": "Hello, World!"}
    return JsonResponse(data)

from django.views import View
from django.http import JsonResponse

class MyView(View):
    def get(self, request):
        data = {"message": "Hello, World!"}
        return JsonResponse(data)

    def post(self, request):
        # Handle POST request
        pass

For simple testing, FBV is more convenient. For example, a simple view that returns a JSON response. However, it would be more convenient to use CBV for composing/group Views that have similar behaviors. Code reuse makes the project more readable and maintainable. It is the OOP concept: composition. Another example is the Model inheritance.

CBVs allow for better organization of views that share similar behaviors through inheritance and mixins. Logging function calls can help understand the flow in more complex CBVs.

About CBV

There is also a set of built-in mixings. Mixings are classes with some methods that can be shared but are not intended to be used alone or in the base class. For example, LoginRequiredMixin, PermissionRequiredMinx are Django built-in mixins.

CBV with mixins covers additional functionalities, such as authentication, permissions, and more. These mixins can be combined to create powerful views that maintain clean and readable code.

Example of CBV with mixins:

class CBView(SomeMixins,View):
  def dispatch(self)
    pass

Besides the official doc guide, a quick overview of classed-based view members is found at Classy Class-Based Views website

Context and Template

In Django, the Context is a crucial component used for composing HTML content that will be returned as an HTTP response. When rendering a template, the context acts as a bridge between the data from your views (often from models) and the presentation logic defined in the templates.

from django.shortcuts import render

def my_view(request):
    context = {
        'title': 'My Page Title',
        'items': ['Item 1', 'Item 2', 'Item 3'],
    }
    return render(request, 'my_template.html', context)

And in HTML:

<h1>{{ title }}</h1>
<ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

Django's templating engine processes the HTML file and replaces the variables defined in the context with their corresponding values. This mechanism allows for the dynamic generation of HTML, making it possible to create responsive and interactive web applications.

Pagination

While pagination is often associated with the concept of QuerySet in Django's ORM, it is also a common feature utilized within views. Pagination helps manage large datasets by breaking them into smaller, manageable chunks, allowing users to navigate through data without overwhelming them with information in a single HTTP response.

Django provides a built-in Paginator which borrows from the Functional Programming concept. Paginator acts on a QuerySet and returns a Page object. The Page object can be used in the template to render the pagination. It is like a generator in Python and evaluates the QuerySet lazily.

This means that items are not fetched from the database until they are actually needed, optimizing performance and reducing memory usage. The Page object behaves similarly to a generator in Python, enabling efficient handling of large datasets.

from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Item

def item_list(request):
    item_list = Item.objects.all()  # Get all items
    paginator = Paginator(item_list, 10)  # Show 10 items per page

    page_number = request.GET.get('page')  # Get the page number from the query string
    page_obj = paginator.get_page(page_number)  # Get the corresponding Page object

    return render(request, 'item_list.html', {'page_obj': page_obj})

HTTP request object

The HTTP request object is a dictionary-like object that contains all the information about the incoming request. It is passed to the view as the first parameter. You can access various attributes such as:

  • request.method – The HTTP method (GET, POST, etc.)
  • request.GET – A dictionary-like object containing GET parameters
  • request.POST – A dictionary-like object containing POST parameters
  • request.COOKIES – A dictionary containing all cookies sent with the request
  • request.META – A dictionary containing all available HTTP headers

Full Documentation about Request and Response is found here

Request and Response are the cargo/envelope of the HTTP communication. The request object is a dictionary-like object that contains all the information about the request. It is passed to the view as the first parameter.

HTTP response object

Django provides different response classes to handle various types of responses:

HttpResponse: The most basic response class. One can return plain text, HTML content, or any other type of content.

  • HTTPResponse
    • render(), which is a shortcut to rendering a template and returning an HttpResponse object with the rendered content.
  • JsonResponse: A subclass of HttpResponse that is specifically designed to return JSON data. It automatically sets the Content-Type header to application/json.
  • HttpResponseBadRequest: This response is used to indicate a bad request (HTTP status code 400).
  • HttpResponseRedirect: Used to redirect users to a different URL, typically after a successful form submission.
from django.http import HttpResponse

def my_view(request):
    return HttpResponse("<h1>Hello, World!</h1>")

from django.http import JsonResponse

def my_json_view(request):
    data = {"message": "Hello, JSON!"}
    return JsonResponse(data)

Conclusion

By leveraging Django's powerful view system and URL routing, developers can create dynamic web applications that respond effectively to user interactions.

Your thoughts...