Django

Key features

  • Admin Interface (CRUD)

  • Templating

  • Form handling

  • Internationalization

  • Session, user management, role-based permissions

  • Object-relational mapping (ORM)

  • Testing Framework

  • Fantastic Documentation

Architecture

  • Models: It describes your database schema and your data structure

  • Views: It controls what a user sees, the view retrieves data from appropriate models and execute any calculation made to the data and pass it to the template

  • Templates: It determines how the user sees it. It describes how the data received from the views should be changed or formatted for display on the page

  • Controller: The Django framework and URL parsing

MVC

Django closely follows the MVC (Model View Controller) design pattern, however, it does use its own logic in the implementation.

Because the “C” is handled by the framework itself and most of the excitement in Django happens in models, templates, and views, Django is often referred to as an MTV framework. In the MTV development pattern:

  • Model: the data access layer. Contains anything and everything about the data: how to access it, how to validate it, which behaviors it has, and the relationships between the data.

  • Template: the presentation layer. Contains presentation-related decisions: how something should be displayed on a Web page or other type of document.

  • View: the business logic layer. Contains the logic that accesses the model and defers to the appropriate template(s). You can think of it as the bridge between models and templates.

Further reading: https://djangobook.com/model-view-controller-design-pattern/

Requests processing

Whenever a request is made by a user, it goes through the following steps:

  • Django determines the root URLconf module to use. Ordinarily, this is the value of the ROOT_URLCONF setting, but if the incoming HttpRequest object has a urlconf attribute (set by middleware), its value will be used in place of the ROOT_URLCONF setting.

  • Django loads that Python module and looks for the variable urlpatterns. This should be a Python list of django.urls.path()and/or django.urls.re_path() instances.

  • Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL.

  • Once one of the URL patterns matches, Django imports and calls the given view, which is a simple Python function (or a class-based view). The view gets passed the following arguments:

    • An instance of HttpRequest.

    • If the matched URL pattern returned no named groups, then the matches from the regular expression are provided as positional arguments.

    • The keyword arguments are made up of any named parts matched by the path expression, overridden by any arguments specified in the optional kwargs argument to django.urls.path() or django.urls.re_path().

    • If no URL pattern matches, or if an exception is raised during any point in this process, Django invokes an appropriate error-handling view.

Inheritance styles in Django

In Django, there are 3 possible inheritance styles

  • Abstract base classes: When you only wants parent’s class to hold information that you don’t want to type out for each child model

  • Multi-table Inheritance: If you are sub-classing an existing model and need each model to have its own database table

  • Proxy models: When you only want to modify the Python level behavior of the model, without changing the model’s fields

Signals

Allow to execute some piece of code based on some action or event occurred in the framework.

Ex.: new user register, on delete of a record.

Some inbuilt signals in Django:

  • pre_save and post_save.

  • pre_delete and post_delete

  • pre_request and post_request

  • pre_request and post_request

Middleware

Function that acts on or transforms a request/response before/after it passes through the view layer.

Each middleware component is responsible for doing some specific function. Example: Django includes a middleware component, AuthenticationMiddleware, that associates users with requests using sessions.

Some usage of middlewares in Django is:

  • It can be used for Session management,

  • User authentication can be done with the help of this.

  • It helps in Cross-site request forgery protection

  • Content Gzipping, etc.

Middleware order and layering

During the request phase, before calling the view, Django applies middleware in the order it’s defined in MIDDLEWARE, top-down.

If one of the layers decides to short-circuit and return a response without ever calling its get_response, none of the layers of the onion inside that layer (including the view) will see the request or the response. The response will only return through the same layers that the request passed in through.

Sessions

Intro

All communication between web browsers and servers is via the HTTP protocol, which is stateless. Ie., messages between the client and server are completely independent of each other— there is no notion of "sequence" or behavior based on previous messages. As a result, if you want to have a site that keeps track of the ongoing relationships with a client, you need to implement that yourself.

Definition

Sessions are the mechanism for keeping track of the "state" between the site and a particular browser.

Sessions allow you to store arbitrary data per browser, and have this data available to the site whenever the browser connects. Individual data items associated with the session are then referenced by a "key", which is used both to store and retrieve the data.

Django uses a cookie containing a special session id to identify each browser and its associated session with the site. The actual session data is stored in the site database by default (this is more secure than storing the data in a cookie, where they are more vulnerable to malicious users).

Enabling sessions

Edit INSTALLED_APPS and MIDDLEWARE sections of the project file (foo/foo/settings.py):

INSTALLED_APPS = [
    ...
    'django.contrib.sessions',
    ....

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ....

Using sessions

Access the session attribute in the view from the request parameter (an HttpRequest passed in as the first argument to the view). This session attribute represents the specific connection to the current user (or to be more precise, the connection to the current browser, as identified by the session id in the browser's cookie for this site).

The session attribute is a dictionary-like object that you can read and write as many times as you like in your view, modifying it as wished. You can do all the normal dictionary operations, including clearing all data, testing if a key is present, looping through data, etc. Most of the time though, you'll just use the standard "dictionary" API to get and set values.

Example:

# Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present
my_car = request.session['my_car']

# Get a session value, setting a default if it is not present ('mini')
my_car = request.session.get('my_car', 'mini')

# Set a session value
request.session['my_car'] = 'mini'

# Delete a session value
del request.session['my_car']

Saving session data

By default, Django only saves to the session database and sends the session cookie to the client when the session has been modified (assigned) or deleted.

Example:

# This is detected as an update to the session, so session data is saved.
request.session['my_car'] = 'mini'

If you're updating some information within session data, then Django will not recognize that you've made a change to the session and save the data. In this case you will need to explicitly mark the session as having been modified:

# Session object not directly modified, only data within the session. Session changes not saved!
request.session['my_car']['wheels'] = 'alloy'

# Set session as modified to force data updates/cookie to be saved.
request.session.modified = True

Note: You can change the behavior so the site will update the database/send cookie on every request by adding SESSION_SAVE_EVERY_REQUEST = True

Views

A callable which takes a request and returns a response.

Base vs Generic views

Base class-based views can be thought of as parent views, which can be used by themselves or inherited from. They may not provide all the capabilities required for projects, in which case there are Mixins which extend what base views can do.

Django’s generic views are built off of those base views, and were developed as a shortcut for common usage patterns such as displaying the details of an object. They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself.

Most generic views require the queryset key, which is a QuerySet instance; see Making queries for more information about QuerySet objects.

Class-based Views

Provide an alternative way to implement views as Python objects instead of functions. They do not replace function-based views.

Differences and advantages when compared to function-based views:

  • Organization of code related to specific HTTP methods (GET, POST, etc.) can be addressed by separate methods instead of conditional branching.

  • Object oriented techniques such as mixins (multiple inheritance) can be used to factor code into reusable components.

Using class-based views

A class-based view allows you to respond to different HTTP request methods with different class instance methods, instead of with conditionally branching code inside a single view function.

Handle HTTP GET in a view function:

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

In a class-based view:

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

Django’s URL resolver expects to send the request and associated arguments to a callable function, not a class, therefore class-based views have an as_view() .

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('about/', MyView.as_view()),
]

You can configure class attributes as keyword arguments to the as_view() call in the URLconf:

urlpatterns = [
    path('about/', GreetingView.as_view(greeting="G'day")),
]

dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed

Decorating the class

To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class.

A method on a class isn’t quite the same as a standalone function, so you can’t just apply a function decorator to the method – you need to transform it into a method decorator first. The method_decorator decorator transforms a function decorator into a method decorator so that it can be used on an instance method. For example:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class ProtectedView(TemplateView):
    template_name = 'secret.html'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument name:

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

If you have a set of common decorators used in several places, you can define a list or tuple of decorators and use this instead of invoking method_decorator() multiple times. These two classes are equivalent:

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

The decorators will process a request in the order they are passed to the decorator. In the example, never_cache() will process the request before login_required().

In this example, every instance of ProtectedView will have login protection.

Mixins

A special kind of multiple inheritance in Python where behaviors and attributes of multiple parent classes can be combined.

2 main situations where mixins are used:

  • You want to provide a lot of optional features for a class.

  • You want to use one particular feature in a lot of different classes.

Further reading: What is a Mixin and why are they useful?

Mixins are an excellent way of reusing code across multiple classes, but they come with some cost. The more your code is scattered among mixins, the harder it will be to read a child class.

You can only inherit from one generic view - that is, only one parent class may inherit from View and the rest (if any) should be mixins.

Querysets

Forms

All form classes are created as subclasses of either django.forms.Form or django.forms.ModelForm. You can think of ModelForm as a subclass of Form.

Bound and unbound form instances

The distinction between Bound and unbound forms is important:

  • Unbound form: has no data associated with it. When rendered to the user, it will be empty or will contain default values.

  • Bound form: has submitted data, and hence can be used to tell if that data is valid. If an invalid bound form is rendered, it can include inline error messages telling the user what data to correct.

The form’s is_bound attribute will tell you whether a form has data bound to it or not.

GET and POST

GET and POST are the only HTTP methods to use when dealing with forms.

GET and POST are typically used for different purposes.

GET

Bundles the submitted data into a string, and uses this to compose a URL.

Should be used only for requests that do not affect the state of the system.

Suitable for things like a web search form, because the URLs that represent a GET request can easily be bookmarked, shared, or resubmitted.

Unsuitable for a password form, because the password would appear in the URL, and thus, also in browser history and server logs, all in plain text.

Unsuitable for large quantities of data, or for binary data, such as an image. A Web application that uses GET requests for admin forms is a security risk: it can be easy for an attacker to mimic a form’s request to gain access to sensitive parts of the system. POST, coupled with other protections like Django’s CSRF protection offers more control over access.

POST

The browser bundles up the form data, encodes it for transmission, sends it to the server, and then receives back its response.

Templates

Generate HTML dynamically

Contains the static parts of the desired HTML output as well as some special syntax describing how dynamic content will be inserted.

A Django project can be configured with one or several template engines (or even zero if you don’t use templates). Django ships built-in backends for its own template system, creatively called the Django template language (DTL), and for the popular alternative Jinja2. Backends for other template languages may be available from third-parties.

Components

Engine

Encapsulates an instance of the Django template system. The main reason for instantiating an Engine directly is to use the Django template language outside of a Django project.

Template

A compiled template

Context

holds some metadata in addition to the context data. It is passed to Template.render() for rendering a template.

Loaders

Responsible for locating templates, loading them, and returning Template objects.

Context processors

Functions that receive the current HttpRequest as an argument and return a dict of data to be added to the rendering context.

Add common data shared by all templates to the context without repeating code in every view.

Implementing a custom context processor is as simple as defining a function.

Last updated