Published on

Get to know useful Django features

5061 words26 min read
Authors

Get to know useful Django features

Django is a powerful web framework that makes it easy to build complex web applications. One of the things that makes Django so powerful is its ability to handle forms, formsets, inlines, filters, querysets, and templates. In this blog post, we're going to take a closer look at each of these topics and see how they can be used to build amazing web applications.

Forms

Forms are the most common way to collect user input in a web application. Django provides a powerful form framework that makes it easy to create forms and validate user input. In this section, we'll take a look at how to create a simple form and how to validate user input.

Creating a form

To create a form, we need to create a class that inherits from the ModelForm class. The ModelForm class is a subclass of the Form class. The ModelForm class provides a set of helper methods that make it easy to create forms and validate user input. For example, the ModelForm class provides a save() method that saves the form data to the database. The ModelForm class also provides a clean() method that validates user input. For example, let's take assume a Model called User that has some fields. We can create a form for the User model.

from django import forms
from .models import User

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email']
  • The Meta class is used to define the model and fields that the form will be based on. You can exclude fields from the form by using the exclude attribute instead of the fields attribute.
  • There are other features that can be used to customize the form. For example, you can use the widgets attribute to specify the type of widget that should be used for each field. You can also use the labels attribute to specify the label for each field.
  • Furthermore, there are different form fields. For example, the CharField is used to represent a text field. The EmailField is used to represent an email field. The DateField is used to represent a date field. The DateTimeField is used to represent a date and time field etc. Let's take a look at how to use these fields, widgets, labels in our Form.
from django import forms
from .models import User

class UserForm(forms.ModelForm):
    first_name = forms.CharField(label='First Name', max_length=100, widget=forms.TextInput(
        attrs={
            'class': 'form-control',
            'placeholder': 'Enter your first name'
        }
    ))
    last_name = forms.CharField(label='Last Name', max_length=100, widget=forms.TextInput(
        attrs={
            'class': 'form-control',
            'placeholder': 'Enter your last name'
        }
    ))
    email = forms.EmailField(label='Email', widget=forms.EmailInput(
        attrs={
            'class': 'form-control',
            'placeholder': 'Enter your email'
        }
    ))
    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email']
        exclude = ['date_joined']

  • There are many other fields and widgets that can be used according to the need. You can refer to the documentation for more information.

How to use the form in a view ?

To use the form in a view, we need to create a view function that takes a request as an argument. The view function should return a response that contains the form. For example, let's create a view function that returns a form.

from django.shortcuts import render
from .forms import UserForm

def user_form_view(request):
    form = UserForm()
    return render(request, 'user_form.html', {'form': form})
  • The render() function is used to render a template. The render() function takes three arguments. The first argument is the request. The second argument is the template name. The third argument is a dictionary that contains the context. The context is a dictionary that contains the variables that will be available in the template. In this case, the context contains the form variable.
  • The user_form.html template can be created as follows.
{% extends 'base.html' %} {% block content %}
<form method="POST">
  {% csrf_token %} {{ form.as_p }}
  <button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}
  • The form.as_p tag is used to render the form as a paragraph. The form.as_table tag is used to render the form as a table. The form.as_ul tag is used to render the form as an unordered list.
  • The csrf_token tag is used to render the CSRF token. The CSRF token is used to prevent Cross-Site Request Forgery attacks. The CSRF token is a hidden field that is used to validate the form. The CSRF token is automatically added to the form when the method attribute is set to POST. It's important to add the CSRF token to the form. Otherwise, the form will not be submitted because the CSRF token will not be validated. You can read more about CSRF tokens here.

How to validate user input ?

Now the above user_form_view function is used to render the form. However, the form is not used to collect user input. To collect user input, we need to use the request.POST attribute. The request.POST attribute is a dictionary that contains the form data. For example, let's modify the user_form_view function to collect user input.

from django.shortcuts import render
from .forms import UserForm

def user_form_view(request):
    form = UserForm()
    if request.method == 'POST':
        form = UserForm(request.POST)
        if form.is_valid():
            form.save()
    return render(request, 'user_form.html', {'form': form})
  • The form.is_valid() method is used to validate user input. If the user input is valid, the form.save() method is used to save the form data to the database. If the user input is invalid, the form.errors attribute is used to get the errors.

  • Sometimes, you might want to get the data from the form without saving it to the database. In this case, you can use the form.cleaned_data attribute. The form.cleaned_data attribute is a dictionary that contains the form data. For example, let's modify the user_form_view function to get the data from the form without saving it to the database.

from django.shortcuts import render
from .forms import UserForm

def user_form_view(request):
    form = UserForm()
    if request.method == 'POST':
        form = UserForm(request.POST)
        if form.is_valid():
            first_name = form.cleaned_data['first_name']
            last_name = form.cleaned_data['last_name']
            email = form.cleaned_data['email']
    return render(request, 'user_form.html', {'form': form})

Note: The form.cleaned_data attribute is only available if the form.is_valid() method returns True. And you can only use the get() method to get the data from the form.cleaned_data attribute. For example, first_name = form.cleaned_data.get('first_name').

How to set initial values for a form ?

Sometimes, you might want to set initial values for a form. For example, let's say you want to create a form that allows users to update their profile or to help user fill-in less predicatable information, like his birthday which could be taken from the database, we can customize the form to set the initial values for the fields. For example, let's modify the user_form_view function to set the initial values for the form.

from django.shortcuts import render
from .forms import UserForm

def user_form_view(request):
    form = UserForm()
    if request.method == 'POST':
        user = User.objects.get(id=request.user.id)
        form = UserForm(request.POST)
        if form.is_valid():
            form.save()
    else:
        form = UserForm(initial={'birth_date': user.birth_date.strftime('%Y-%m-%d')})
    return render(request, 'user_form.html', {'form': form})
  • The form = UserForm(initial={'birth_date': user.birth_date.strftime('%Y-%m-%d')}) line is used to set the initial value for the birth_date field.

Note: The request.user attribute is used to get the current user. The request.user attribute is only available if the user is authenticated. You can read more about authentication here.

Formsets

Formsets are a way to manage multiple forms in a single view. They are particularly useful when you need to handle a large number of forms in a single view. For example, imagine you have a website that allows users to create a list of items. Instead of creating a separate form for each item, you can use a formset to handle all of the forms in a single view. Let's take a look at how to use formsets.

Preparing a formset

Let's say we have the User model that we created in the previous section. We want to create a formset that allows users to add his/her social media accounts. The User model has the following fields.

class User(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.EmailField()
    date_joined = models.DateTimeField(auto_now_add=True)
  • We want to create a formset that allows users to add his/her social media accounts. The SocialMediaAccount model has the following fields.
class SocialMediaAccount(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    url = models.URLField()
  • The SocialMediaAccount model has a foreign key to the User model. The user field is used to store the user that owns the social media account.

What I have to come to use is the inlineformset_factory function to create a formset. The inlineformset_factory function takes three arguments. The first argument is the parent model. The second argument is the child model. For example, let's create a formset that allows users to add his/her social media accounts. Let's create a new view called user_formset_view in the views.py file.

from django.shortcuts import render
from .forms import UserForm
from .models import User, SocialMediaAccount

def user_formset_view(request):

    UserFormSet = inlineformset_factory(User, SocialMediaAccount, fields=('name', 'url'), extra=1,
    can_delete=False)
    if request.method == 'POST':
        form = UserForm(request.POST)
        formset = UserFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            user = form.save()
            for form in formset:
                social_media_account = form.save(commit=False)
                social_media_account.user = user
                social_media_account.save()
    else:
        form = UserForm()
        formset = UserFormSet()
    return render(request, 'user_formset.html', {'formset': formset}, {'form': form})

So what's happening here? Let me explain is step by step.

  • The inlineformset_factory function is used to create a formset. The inlineformset_factory function takes three arguments.

    • The first argument isthe parent model.
    • The second argument is the child model.
    • The extra argument is used to specify the number of extra forms that will be added to the formset.
    • The can_delete argument is used to specify whether the user can delete the forms in the formset.
    • The fields argument is used to specify the fields that will be displayed in the formset.
  • Then, we check if the request method is POST. If the request method is POST, we create a UserForm instance and a UserFormSet instance.

  • Then, we check if the UserForm instance and the UserFormSet instance are valid. If they are valid, we save the UserForm instance to the database. Then, we loop through the UserFormSet instance and save each form to the database.

  • If the request method is not POST, we create a UserForm instance and a UserFormSet instance.

  • Finally, we render the user_formset.html template and pass the UserFormSet instance along with the UserForm instance to the template.

What is difference between UserForm and UserFormSet?

When you create a form or formset object and pass it the request.POST data, Django will automatically extract the relevant data from the request and use it to populate the form or formset fields.

For example, when you create a form object using form = UserForm(request.POST), Django will extract the data from the request.POST object that corresponds to the fields in the UserForm, and use that data to populate the form. Similarly, when you create a formset object using formset = UserFormSet(request.POST), Django will extract the data from the request.POST object that corresponds to the fields in the UserFormSet and use that data to populate the formset.

Creating a formset template

Now that we have created a formset, let's create a template to display the formset. Let's create a new file called user_formset.html in the templates folder.

{% extends 'base.html' %} {% block content %}
<h1>User Formset</h1>
<form method="POST">
  {% csrf_token %} {{ form.as_p }} {{ formset.management_form }} {% for form in formset %} {{
  form.as_p }} {% endfor %}
  <input type="submit" value="Submit" />
</form>
{% endblock %}

So what's happening here? Let me explain is step by step.

  • We extend the base.html template.
  • Then, we display the UserForm instance using the form.as_p template tag.
  • Then, we display the UserFormSet management form using the formset.management_form template tag.
  • Then, we loop through the UserFormSet instance and display each form using the form.as_p template tag.
  • Finally, we display a submit button.

This is a very basic implementation of a formset. You can use the same approach to create more complex formsets.

Inlines

In django admin, out of the box has a lot of features and it's pretty easy to use. But if you want to customize the admin site, you can do that too. One of those is the need to add or edit related objects on the same page as the parent object. This is called inlines.

Creating an inline

Let's use the User model and the SocialMediaAccount model to create an inline. Assuming we have registered the User model and the SocialMediaAccount model in the admin site, let's create a new class called SocialMediaAccountInline in the admin.py file.

from django.contrib import admin
from .models import User, SocialMediaAccount

class SocialMediaAccountInline(admin.TabularInline):
    model = SocialMediaAccount
    extra = 1

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    search_fields = ('first_name', 'last_name', 'email')
    list_display = ('first_name', 'last_name', 'email')
    inlines = [SocialMediaAccountInline]

This will add the SocialMediaAccount model as an inline to the User model in the admin site.

Different types of inlines

  • StackedInline - Displays the inline in a stacked format.
  • TabularInline - Displays the inline in a tabular format.

Querysets

Querysets are the core of Django's ORM. Querysets allow you to read data from the database, filter data, order data, and much more. Querysets are lazy. This means that querysets are not evaluated until they are needed. This is a very important concept to understand. So let's dive into it.

Creating a queryset

Let's play around with just the User model just to get a feel for querysets.

  • Get all users
from .models import User

users = User.objects.all()
  • Get a single user
from .models import User

user = User.objects.get(id=1)
  • Okay so we gave the get() method the id of the user we want to get. But what if we want to get the user by email? We can do that too. Better yet, what if we want to get the user through a mixture of fields? It's possible.
from .models import User

user = User.objects.get(
    first_name='John',
    last_name='Doe',
    created_at__year=2020,
)

Filtering

We could only have limited use of get() if we had to get a single user. But what if we want to get multiple users? We can do that too.

To get more fancier and chain different filters, we can use the filter() method.

from .models import User

users = User.objects.filter(
    first_name='John',
    last_name='Doe',
    created_at__year=2020,
)

Ordering

We can order the querysets. Ordering is done by passing the field name to the order_by() method.

from .models import User

users = User.objects.order_by('first_name')

Limiting

We can limit the querysets. Limiting is done by passing the number of items to the all() method. Most often done with slicing.

from .models import User

users = User.objects.all()[:5]

Counting

We can count the querysets. Counting is done by calling the count() method. This is very useful when we want to get the number of items in a queryset.

from .models import User

users = User.objects.count()

Use Q objects

In django we can use Q objects to combine filters. Let's see how we can do that.

from django.db.models import Q

# Find all users who have either 'John' as first name or 'Doe' as last name
users = User.objects.filter(Q(first_name='John') | Q(last_name='Doe'))

# Find all users who have 'John' as first name and 'Doe' as last name
users = User.objects.filter(Q(first_name='John') & Q(last_name='Doe'))

# Find all users who have last name 'Doe' but not 'John' as first name
users = User.objects.filter(Q(last_name='Doe') & ~Q(first_name='John'))

# Find all users who have last name 'Doe' or first name 'John' but not both
users = User.objects.filter(Q(last_name='Doe') | Q(first_name='John')).exclude(first_name='John', last_name='Doe')

Use F objects

In django we can use F objects to compare fields.

from django.db.models import F

# Find all users whose first name is the same as their last name
users = User.objects.filter(first_name=F('last_name'))

# Find all users whose age is greater than the age of their manager
users = User.objects.filter(age__gt=F('manager__age'))

# Find all users whose age is at least twice the age of their manager
users = User.objects.filter(age__gte=F('manager__age')*2)

# Find all users whose birth date is before their registration date
users = User.objects.filter(birth_date__lt=F('registration_date'))

# Find all users whose last login date is more recent than their registration date
users = User.objects.filter(last_login__gt=F('registration_date'))

Use Q objects and F objects together

We can also use Q objects and F objects together. In very complex queries, this is very useful.

from django.db.models import Q, F

# Find all users whose first name is the same as their last name and their age is greater than 18
users = User.objects.filter(Q(first_name=F('last_name')) & Q(age__gt=18))

Reverse relationships

In django it's pretty straightforward to get objects from parent to child. But what if we want to get objects from child to parent? There is a way to accomplish that.

Let's use the User model and the SocialMediaAccount model to get objects from child to parent.

from .models import User, SocialMediaAccount

# Get all social media accounts of a user
user = User.objects.get(id=1)
social_media_accounts = user.social_media_accounts.all()

What if we want to get all users of a social media account? There is a method to access attributes of the a parent object from a child object.

Let's consider the example of Building and Room models. A building can have many rooms. But a room can only have one building.

from .models import Building, Room

# Get all rooms of a building
building = Building.objects.get(id=1)
rooms = building.room_set.all()

# Get the building of a room
room = Room.objects.get(id=1)
building = room.building
  • But if the relationship is a ManyToManyField, then we can use through to get the related objects, or we can use related_name to get the related objects.

Let's consider the example of User and Group models. A user can be in many groups. And a group can have many users.

from .models import User, Group

users = User.objects.filter(name='John', user_groups=g.id)
  • Here we are using user_groups as the related_name of the ManyToManyField in the User model.

Note: If you want to get the related objects through a ManyToManyField, then you can use through to get the related objects, or you can use related_name to get the related objects.

Templates

In django we can use templates to render dynamic content. They are very powerful and easy to use. The syntax is very similar to HTML. It's also called Django Template Language (DTL) it's pretty similar to Jinja2.

Let's go through set up and some basic syntax.

  • First we need to create a template file. Let's create a templates folder in the root of our project. Then create a users folder inside the templates folder. Then create a index.html file inside the users folder.

  • Then we need to add the TEMPLATES setting in the settings.py file.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Note: We need to add the DIRS setting to tell django where to look for the templates.

  • To make use of the templates that we create, we need to create a view. Let's create a views.py file in the users folder.
from django.shortcuts import render
from .models import User

def index(request):
    users = User.objects.all()
    return render(request, 'users/index.html', {'users': users})
  • Then in the urls.py file of the users app, we need to add the index view.
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
]
  • In the index.html file, we can use the users variable that we passed to the template.
{% for user in users %}
<p>{{ user.first_name }} {{ user.last_name }}</p>
{% endfor %}

Template tags

In django we can use template tags to do some logic in the template. I won't be covering all the template tags, just the ones that is commonly used.

  • if tag
{% if user.age > 18 %}
<p>{{ user.first_name }} {{ user.last_name }} is an adult</p>
{% else %}
<p>{{ user.first_name }} {{ user.last_name }} is a minor</p>
{% endif %}
  • for tag
{% for user in users %}
<p>{{ user.first_name }} {{ user.last_name }}</p>
{% endfor %}
  • url tag
<a href="{% url 'index' %}">Home</a>

Note: We can use the url tag to get the url of a view. We need to pass the name of the view as the argument.

  • static tag
<link rel="stylesheet" href="{% static 'css/style.css' %}" />

Note: We can use the static tag to get the url of a static file. We need to pass the path of the static file as the argument. By default django looks for the static files in the static folder in the root of the project.

  • block tag
{% block content %}
<h1>Home</h1>
{% endblock %}

Note: We can use the block tag to define a block in the template. Every template that extends another template must have a block tag. If we don't have a block tag, then django will throw an error.

  • extends tag
{% extends 'base.html' %}

Pro Tip: Every template could be extended from a base.html file. This is a good practice. We can use the block tag to override the blocks in the base.html file.

Let's demonstrate how we can use the block and extends tags. Remember block tag is used to define a block in the template, which means when a child template extends a parent template, then the child template can override the blocks in the parent template or add content inside that block thereby extending the parent template.

This is a great way to reuse the code, the child template is able to customize specific parts of the parent template. Without having to copy the whole template again and again.

  • First we need to create a base.html file in the templates folder. Then we need to add the block tag in the base.html file.
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Base</title>
  </head>
  <body>
    <header>
      <nav>
        <ul>
          <li><a href="{% url 'index' %}">Home</a></li>
          <li><a href="{% url 'users:index' %}">Users</a></li>
        </ul>
      </nav>
    </header>
    <main>{% block content %} {% endblock %}</main>
  </body>
</html>
  • Then we need to create a index.html file in the users folder. Then we need to add the extends tag in the index.html file.
{% extends 'base.html' %} {% block content %}
<h1>Home</h1>
{% endblock %}

Note: We need to add the block tag in the base.html file and the extends tag in the index.html file.

  • This means the index.html file extends the base.html file. So the index.html file will have the content of the base.html file and it's own content embedded in the block tag.

Template filters

In django we can use template filters to modify the data in the template. I won't be covering all the template filters, just the ones that is commonly used.

  • date filter
<p>{{ user.created_at|date }}</p>

Note: We can use the date filter to format the date. We can pass the format as the argument.

  • time filter
<p>{{ user.created_at|time }}</p>

Note: We can use the time filter to format the time. We can pass the format as the argument.

  • length filter
<p>{{ user.first_name|length }}</p>

Note: We can use the length filter to get the length of a string.

  • There are many other filters that we can use in django. We can check the documentation for more information.

Django Cache

In the world of web development, caching is a very important topic. Caching is a way to store data in a temporary storage so that we can access it faster. We need to use caching when we have data that is not changing frequently. For example, if we have a list of users, then we can cache the list of users so that we don't have to query the database every time we need the list of users. This will improve the performance of our application.

Django provides a very flexible caching system. We can use different caching backends. The integration with the caching system is very easy. We can use the caching system in our views, templates, models, etc. Let's use Redis as the caching backend. Since Redis is an in-memory database, it is very fast and pretty popular in the industry.

  • First we need to install django-redis and redis packages.
pip install django-redis redis
  • Then we need to add the django-redis package to the INSTALLED_APPS in the settings.py file.
INSTALLED_APPS = [
    ...
    'django_redis',
]
  • Then we need to add the CACHES setting in the settings.py file.
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

Note: We need to add the CACHES setting in the settings.py file. We need to pass the BACKEND and LOCATION as the arguments.

Let's start using the caching system in our application. We will use the caching system in our index view.

  • First we need to import the cache from the django.core.cache module.
from django.core.cache import cache
  • Then we need to add the cache decorator to the index view.
@cache_page(60 * 15)

def index(request):
    users = User.objects.all()
    return render(request, 'users/index.html', {'users': users})

So what's happening here? We are using the cache_page decorator to cache the index view. We are passing the 60 * 15 as the argument. This means the index view will be cached for 15 minutes. So if we access the index view within 15 minutes, then the view will be served from the cache. If we access the index view after 15 minutes, then the view will be served from the database.

  • The next thing we need to do is to add the cache tag in the index.html file.
{% load cache %}
  • Then we need to add the cache tag in the index.html file.
{% cache 60 * 15 users %}
<ul>
  {% for user in users %}
  <li>{{ user.first_name }} {{ user.last_name }}</li>
  {% endfor %}
</ul>
{% endcache %}

So what's happening here? We are using the cache tag to cache the users queryset. We are passing the 60 * 15 as the argument. This means the users queryset will be cached for 15 minutes. So if we access the index view within 15 minutes, then the users queryset will be served from the cache. If we access the index view after 15 minutes, then the users queryset will be served from the database.

NOTE: the purpose of caching in template and view is different. In view, we are caching so that we don't recall the database every time we access the view. In template, we are caching so that we don't recall the database every time we access the template, only if the data is not altered.

Django Pagination

Django provides a very easy way to paginate the data. We can use the Paginator class to paginate the data. Let's see how we can use the Paginator class to paginate the data.

  • First we need to import the Paginator class from the django.core.paginator module.
from django.core.paginator import Paginator
  • Then we need to create a Paginator object and pass the users queryset and the per_page as the arguments.
paginator = Paginator(users, 10)
  • Then we need to get the page number from the request.GET dictionary.
page = request.GET.get('page')
  • Then we need to get the page object from the paginator object.
page_obj = paginator.get_page(page)
  • Then we need to pass the page_obj queryset to the render function.
return render(request, 'users/index.html', {'page_obj': page_obj})
  • Then we need to add the page_obj to the index.html file.
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %} {% for page in page_obj.paginator.page_range %}
<a href="?page={{ page }}">{{ page }}</a>
{% endfor %} {% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
  • Then we need to add the page_obj to the index.html file.
{% for user in page_obj %}
<li>{{ user.first_name }} {{ user.last_name }}</li>
{% endfor %}

Django Filtersets

Django provides a very easy way to filter the data. We can use the FilterSet class to filter the data. Let's see how we can use the FilterSet class. This acts more like the detailed search functionality. We can filter the data based on the fields.

  • First we need to install the django-filter package.
pip install django-filter
  • Then we need to add the django-filter package to the INSTALLED_APPS in the settings.py file.
INSTALLED_APPS = [
    ...
    'django_filters',
]
  • Then we need to create a UserFilter class and inherit the FilterSet class.
from django_filters import FilterSet

class UserFilter(FilterSet):
    class Meta:
        model = User
        fields = ['first_name', 'last_name']
  • Then we need to add the UserFilter class to the index view.
def index(request):
    users = User.objects.all()
    user_filter = UserFilter(request.GET, queryset=users)
    return render(request, 'users/index.html', {'user_filter': user_filter})
  • Then we need to add the user_filter to the index.html file.
<form action="" method="GET">
  {{ user_filter.form }}
  <button type="submit">Filter</button>
</form>
  • That is it. Now we can filter the data based on the fields.

Conclusion

In this post, I have covered some important and useful features of Django, like Django forms, formsets, Django caching, Django pagination, and Django filtersets. I hope you have enjoyed this post. These are the features I have used throughout my Django projects. I only provided simple examples to get an idea of how these features work. There are other features like Django signal, messages, logging, etc. When I get to use these features, I will write a post about them.

References