Get to know useful Django features
This post contains useful information regarding different features in django
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']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.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.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']
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})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.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 %}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.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_dataattribute is only available if theform.is_valid()method returnsTrue. And you can only use theget()method to get the data from theform.cleaned_dataattribute. 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})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.userattribute is used to get the current user. Therequest.userattribute 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)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()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.
extra argument is used to specify the number of extra forms that will be added to the formset.can_delete argument is used to specify whether the user can delete the forms in the formset.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.
base.html template.UserForm instance using the form.as_p template tag.UserFormSet management form using the formset.management_form template tag.UserFormSet instance and display each form using the form.as_p template tag.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.
from .models import User
users = User.objects.all()from .models import User
user = User.objects.get(id=1)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.buildingthrough 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)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
throughto get the related objects, or you can userelated_nameto 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
DIRSsetting to tell django where to look for the templates.
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})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'),
]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
urltag 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
statictag 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 thestaticfolder in the root of the project.
block tag{% block content %}
<h1>Home</h1>
{% endblock %}Note: We can use the
blocktag to define a block in the template. Every template that extends another template must have ablocktag. If we don't have ablocktag, then django will throw an error.
extends tag{% extends 'base.html' %}Pro Tip: Every template could be extended from a
base.htmlfile. This is a good practice. We can use theblocktag to override the blocks in thebase.htmlfile.
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.
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>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
blocktag in thebase.htmlfile and theextendstag in theindex.htmlfile.
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
datefilter 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
timefilter 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
lengthfilter to get the length of a string.
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.
django-redis and redis packages.pip install django-redis redisdjango-redis package to the INSTALLED_APPS in the settings.py file.INSTALLED_APPS = [
...
'django_redis',
]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
CACHESsetting in thesettings.pyfile. We need to pass theBACKENDandLOCATIONas the arguments.
Let's start using the caching system in our application. We will use the caching system in our index view.
cache from the django.core.cache module.from django.core.cache import cachecache 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.
cache tag in the index.html file.{% load cache %}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.
Paginator class from the django.core.paginator module.from django.core.paginator import PaginatorPaginator object and pass the users queryset and the per_page as the arguments.paginator = Paginator(users, 10)page number from the request.GET dictionary.page = request.GET.get('page')page object from the paginator object.page_obj = paginator.get_page(page)page_obj queryset to the render function.return render(request, 'users/index.html', {'page_obj': page_obj})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 %}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.
django-filter package.pip install django-filterdjango-filter package to the INSTALLED_APPS in the settings.py file.INSTALLED_APPS = [
...
'django_filters',
]UserFilter class and inherit the FilterSet class.from django_filters import FilterSet
class UserFilter(FilterSet):
class Meta:
model = User
fields = ['first_name', 'last_name']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})user_filter to the index.html file.<form action="" method="GET">
{{ user_filter.form }}
<button type="submit">Filter</button>
</form>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.