The best way to learn Django, or any skill, is by applying the knowledge you have by creating functional projects.
Django is the most used Python web development framework. Its built-in features and extensive amount of 3rd party packages have made it one of the most loved web frameworks all around the world.
It is fast, reliable, and has a lot of built-in features. For instance, a hole authentication system, that lets you focus on the main features of your app. Also, you can install external packages to accomplish even more complex tasks, like Django-allauth
, which allows you to register users with their social accounts.
But let’s expose some facts. Django is such a huge framework that sometimes is challenging to get started with it.
So today you are going to build from scratch a totally functional Django application.
By the end of this tutorial, you will:
- Have written a URL shortener app
- Understand the Django MVT pattern
- Have learned the workflow of creating a project
Pre-requirements
All of the following requirements are optional and will help you to follow along with the tutorial. But if you don’t have experience with any of these, don’t worry. The most important step is the first.
- Basic understanding of UNIX commands (ls, cd, rm, touch)
- Basic understanding of Python classes and functions
- Python installed on your computer (Maybe obvious but I had to include it)
- It would be great if you already have built something with Django
All the working code will be available on this Github repo.
Now that you have crystal clear the previous concepts, let’s get into the matter.
Project statement
In this tutorial, you are going to build a URL shortener. Basically, a URL shortener is a service that takes a long URL and turns it into a compact one.
For example, if you want to share a tweet and want to include a link to your website, but you are facing the characters limit, you could use an URL shortener.
Let’s see it with a graphic.
As you can see, the URL shortener gets a long URL and returns a short one. That’s exactly what you are going to build today.
With this project, you will practice the usage of the MVT pattern, learn the basics of database design with Django models, and learn how to show information to the user through views, URLs, and templates.
Structure of a Django project
Basically, a Django website is built upon a single project and multiple separated apps. Each one of these apps has specific functionality and it’s able to work by itself.
Let’s imagine a complex web application like Stackoverflow. Its functionality is based on two main aspects.
- Users management: Sign-in, Log out, Reputation, Permissions
- Forum: Questions, answers, tags, Filters
So following the Django website structure, the project would be named StackOverflow which has two main apps. The users’ app and the forum app.
Each of these apps has self-contained functionality. That means that both contain all the code that they need to work properly.
That includes models (database structure), views (requests and responses), specific URL patterns, and of course, templates and static files (images, CSS, JavaScript). This means that any Django app could be reused since they are able to work by themself.
Shortening, a project refers to a set of configurations and apps that is meant to build a Web Application. On the other hand, a Django app is a part of a project, which is self-contained (has everything it needs to work), and its purpose is to perform a specific operation.
Set Up a Django project
In this section, you are going to set up a Django project. For that purpose, you are going to use different tools like a Virtual environment to organize Python dependencies and the most important Django scripts. Django-admin and manage.py
Virtual environment
I always recommend working with virtual environments when building apps with Django. It is the most efficient way to maintain a specific set of dependencies. But its main purpose is to isolate the development packages from the global ones.
So let’s create a virtual environment with python the built-in form command.
Note: This method requires Python 3.6 or newer versions to work.
python -m venv .venv
This command uses the python -m or python –mod command. Fundamentally it runs a module or library, as a script. According to the meaning of this command, venv is the library that we’re running, and .venv refers to the name of the virtual environment we want to create.
So in plain language, this command means.
Hey Python, run as a script the built-in library venv and create a virtualenv name .venv
Now, it’s time to activate the virtual environment we just created, with the following command.
source .venv/bin/activate
To assert that you have zero packages installed in the new venv, you run.
pip freeze
If you activated the virtual environment correctly, you won’t get any output. That’s because we haven’t installed anything yet.
Let’s get into Django
In order to create our URL shortener application, we are going to start by installing the Django package. Django is a third-party package, therefore we need to install it with Pip (Pip Installs Packages).
$ pip install django
Collecting django
Downloading Django-3.2.1-py3-none-any.whl (7.9 MB)
|████████████████████████████████| 7.9 MB 344 kB/s
Collecting asgiref<4,>=3.3.2
Using cached asgiref-3.3.4-py3-none-any.whl (22 kB)
Collecting sqlparse>=0.2.2
Using cached sqlparse-0.4.1-py3-none-any.whl (42 kB)
Collecting pytz
Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
Installing collected packages: asgiref, sqlparse, pytz, django
Successfully installed asgiref-3.3.4 django-3.2.1 pytz-2021.1 sqlparse-0.4.1
Note: Remember that $ is nothing but your shell symbol.
To check if the installation went correctly, we check again the installed packages of our venv.
$ pip freeze
asgiref==3.3.4
Django==3.2.1
pytz==2021.1
sqlparse==0.4.1
Don’t worry if the versions you get are different from mine. If Django stills on version 3.x you can continue without any problem.
Starting a Django project
Once you have installed Django, it’s time to create the structure of the URL shortener website. Do you remember what is a Django project? Let’s create one by running the following command.
django-admin startproject config
Explaining all about this command, django-admin
is a command-line utility that performs all the needed tasks to create a Django project. The “startproject” part is the command ran by the Django-admin utility, and config is the name of the project we’re going to create.
Is important to emphasize that config can be any name you want. The reason I use config as the name of this project is just because of convenience. It’s nice to switch between projects and still have the same naming convention. So don’t be afraid of using other project names whenever you want.
As you may notice now you have a config/ folder and inside, there are many files. Later we will see the file structure of the project. For now, let’s enter the project directory and run the local server.
cd config/
The most important file you will be using is the manage.py script. It has the same functionality as django-admin, but the main advantage of using it is that lets you manage the settings when running the project.
Now let’s see if everything is working properly.
python manage.py runserver
Creating the Url shortener app
It’s time to create the main app of the project. You are going to use the manage.py file to accomplish this task.
python manage.py startapp urlshortener
This creates a Django app, with the name urlshortener. If you run the tree command, you’ll get something like this.
.
├── config
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── urlshortener
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
Let’s clarify the different files created until this moment. “config” is the name of our project and it is named like that just for the convention. Inside of config, you get settings.py
, it is the file where you set all the settings of your project. urls.py
is the URLs’ overall configuration inside the project. It defines the URL paths of all the applications inside of the project.
Don’t worry too much about the asgi.py
and wsgi.py
files. These are files that let you configure your application in deployment.
The manage.py
is the python script that lets you run all the available commands of Django-admin.
Taking a look inside urlshortener
which is the name of the app you just created, you may notice there is a weird folder called “migrations/” and some other files that are crucial to the logic of any app.
apps.py
is where the app configuration lives. Usually, you don’t mess around with it, except you are doing pretty advance stuff.
admin.py
is where you register your models to make them visible inside the Django admin panel.
models.py
is the most important one. Inside this module, you must define the models, which (vaguely saying) are the way the data is stored. You’ll hear more about models later.
migrations/
is the folder where Django migrations are stored. We will take an in-depth look later.
tests.py
is the file where the tests are stored. We won’t cover testing in this tutorial.
views.py
is the file that stores views. Basically, it defines how the user will interact with all the aspects of your app.
Installing a Django app
Before continuing, open the settings.py
file and modify the INSTALLED_APPS
variable by adding the urlshortener app.
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Custom apps
'urlshortener',
]
This is a routine process when you create an app. So every time you do it, don’t forget to install it on the project settings.
Understanding the MVT pattern
The Model, View, Template pattern is the software design pattern that Django developers use to create web applications.
It is based on 3 main concepts. Model (data), View (User interaction with data), Template (How the users see the data).
Models are Python classes, which define all the fields and behavior of the data you are willing to store. Normally each model refers to a unique table on the database
Views in their simplest expression, are callables that take a request from the user and generate a response. Between that process occurs the business logic. I know that “business logic” is a pretty obscure concept, so let me explain exactly what it is. Business logic is the way data is created, stored, and deleted, that’s all.
Finally, templates are text documents (Usually Html) that are shown to the users. Its purpose is to present data as clean as possible. Django incorporates a mini-language called Django template language (DTL) which lets you incorporate some of the power of python in text documents.
Create the Shortener model
Once you’ve had a quick grasp of the MVT pattern, let’s get into creating the Django URL shortener from scratch.
First of all, let’s define the shortener model inside of the models.py
file.
'''
Url shortener model
'''
from django.db import models
# Create your models here.
class Shortener(models.Model):
'''
Creates a short url based on the long one
created -> Hour and date a shortener was created
times_followed -> Times the shortened link has been followed
long_url -> The original link
short_url -> shortened link https://domain/(short_url)
'''
created = models.DateTimeField(auto_now_add=True)
times_followed = models.PositiveIntegerField(default=0)
long_url = models.URLField()
short_url = models.CharField(max_length=15, unique=True, blank=True)
class Meta:
ordering = ["-created"]
def __str__(self):
return f'{self.long_url} to {self.short_url}'
I know. It is a pretty huge class, with a lot of weird stuff going on, but don’t despair. I’ll go step by step on each important thing.
Model explanation
First of all, we import the models
module. This module contains all of the functionality we need to create a Django model.
Taking a look at the “Shortener” model, the first thing to note is that it extends models.Model
. In fact, any model in any Django app must be a subclass of the models.Model class.
Then we define all the fields the model will have on the database. The “created” field is the date and time the shortened link is created, therefore we use DateTimeField to create this kind of functionality. We use the argument auto_now_add=True because we want the field to only be altered when the instance is created.
The second field times_followed
refers to the times the shortened URL has been used. It is a PositiveIntegerField and we specify a default of zero. That means that every time an instance has created the times_followed
field, Django will fill out that field with 0.
On the other hand, long_url
refers to the URL the user inputs. It is an URLField, because we only want the user to enter characters of the form: http://yoursite.com
.
The last field is short_url
, and it has interesting details. We specify that it only can be 15 characters long, it must be unique, which means that there can’t be repeated elements in that field. Finally, we indicate that it can be left blank, which means that when working with forms users won’t need to write their own shorten code.
The Meta inner class, tell us how the class must behave, and we set that the ordering (calling Shortener.objects.all()) of the shortener objects will be discriminated by the most recent ones.
The __str__
method tells how the model must be printed. So if we have an object with long_url = “https://geekflare.com/” and shortened part “123456”, and we print it.
https://geekflare.com/ to 123456
Now it’s time to look up a way to save the short link in a random way.
Creating shortening functionality
We are going to create 2 custom functions. The first will generate a random code and the second will prevent getting repeated random codes from the Shortener model. To do this, create a file utils.py inside the “urlshortener” app.
touch utils.py
Inside of this file, we are going to use the choose function from the random built-in module. This facilitates the task of choosing random characters to create the code.
'''
Utilities for Shortener
'''
from django.conf import settings
from random import choice
from string import ascii_letters, digits
# Try to get the value from the settings module
SIZE = getattr(settings, "MAXIMUM_URL_CHARS", 7)
AVAIABLE_CHARS = ascii_letters + digits
def create_random_code(chars=AVAIABLE_CHARS):
"""
Creates a random string with the predetermined size
"""
return "".join(
[choice(chars) for _ in range(SIZE)]
)
As you can see this function returns a random string of the length specified on the settings file or 7 by default. You are using the function getattr to get a variable from the settings module, but without throwing an error if the variable isn’t specified.
Let’s do some math. If we have 7 places where there can be up to 62 available characters for each place, the possible permutations are:
So based on these quick calculations, the shortened part can be filled out up to 2.5 trillion different codes. So we can forget about getting out of random shortened URLs.
Although there can exist such a lot of permutation, there is a small probability of getting repeated shortened parts. This is a problem since we set up the shortened_url
field to be unique. That’s why the following function is so useful.
def create_shortened_url(model_instance):
random_code = create_random_code()
# Gets the model class
model_class = model_instance.__class__
if model_class.objects.filter(short_url=random_code).exists():
# Run the function again
return create_shortened_url(model_instance)
return random_code
Let’s see what’s happening here. The function takes as an argument a “Shortener” model instance. First, the function generates a random code using the create_random_code
. Then it gets the model class and checks if there is any other object that has the same short_url
. If it does it runs itself one more time, but if everything is fine it returns the random_code.
Later you will interact with the shell, in order to take a look at this function closely.
After creating the utility function, let’s use it to create random codes in the shortener model.
Modifying the save method
At the end of the “Shortener” class, you are going to modify the model save method. The save method is called every time an object is saved to the database, so we will see how to use it here.
# Import the function used to create random codes
from .utils import create_shortened_url
# At the end of the Shortener model
def save(self, *args, **kwargs):
# If the short url wasn't specified
if not self.short_url:
# We pass the model instance that is being saved
self.short_url = create_shortened_url(self)
super().save(*args, **kwargs)
The save method is being overwritten, which means you are introducing new functionality to a pre-existing parent method. It is basically telling Django that each time a “Shortener” object is saved and the short_url
isn’t specified, it must be filled with a random code.
Running migrations
Now it’s time to make and run the migrations of the Shortener model. To do that run the following commands in the root project folder.
$ python manage.py makemigrations
Migrations for 'urlshortener':
urlshortener/migrations/0001_initial.py
- Create model Shortener
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, urlshortener
Running migrations:
......
# Apply the URL shortener migrations
Applying urlshortener.0001_initial... OK
For now, you don’t have to worry about what migrations are. Just have in mind that when running these two commands Django creates a db.sqlite database file, based on the models you’ve defined.
Let’s create some objects with the Django shell.
$ python manage.py shell
>>> from urlshortener.models import Shortener
>>> s = Shortener(long_url="https://geekflare.com")
>>> s.short_url
''
>>> s.save()
>>> s.short_url
'kdWFVIc'
>>> s.long_url
'https://geekflare.com'
>>> print(s)
https://geekflare.com to kdWFVIc
That’s pretty much how all shortener objects will work.
Writing views
As I said before, a view is a simple function that takes a request and returns a response. So let’s see how to create a hello world view.
Basic template response
Inside of “urlshortener/views.py” file create a function home_view. aa
'''
Shortener views
'''
from django.shortcuts import render, get_object_or_404 # We will use it later
from django.http import HttpResponse
# Create your views here.
def home_view(request):
return HttpResponse("Hello world")
It returns a simple message “Hello world”. Later you will see how it looks in the browser. Now create a “urls.py”, there will stand all the URL patterns of the app.
touch urls.py
Add the following code.
'''
Urls for shortener app urlshortener/urls.py
'''
from django.urls import path
# Import the home view
from .views import home_view
appname = "shortener"
urlpatterns = [
# Home view
path("", home_view, name="home")
]
The appname variable declares (as its name suggests) the namespacing of the urlshortener app.
Quickly explaining we are importing the path function, which returns an element to include in the urlpatterns of the app. The name attribute is the namespace of the path, that can be called inside templates if necessary.
Now, let’s modify the overall project URLs.
# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# Shortener Urls
path('', include('urlshortener.urls'))
]
Now let’s run the server again.
python manage.py runserver
If you run the server, you’ll get a simple “Hello world” message. This is because you are including the urlpatterns from the URL shortener app, into the overall project.
This is just a starting point. Now it’s time to create a form to let the user create Shortened URLs by themselves.
Creating forms
In Django, a form is a simple class that allows getting input from the User.
You are going to create a forms.py file. It is a convention to store all the forms of the app in that file.
cd urlshortener/
touch forms.py
Inside of that file you are going to create a class “ShortenerForm” which extends from “ModelForm”.
'''
Shortener Forms urlshortener/forms.py
'''
from django import forms
from .models import Shortener
class ShortenerForm(forms.ModelForm):
long_url = forms.URLField(widget=forms.URLInput(
attrs={"class": "form-control form-control-lg", "placeholder": "Your URL to shorten"}))
class Meta:
model = Shortener
fields = ('long_url',)
It is a model form since its purpose is to create a model object from the user input. We also make use of the widget argument, which lets us specify the “class” (class in CSS, not python) attribute. This is because we are going to stylize the app with bootstrap later.
Finishing the views
After building the forms is time to create the final business logic of the application.
Navigate to the views.py
file inside the shortener app, and modify the home_view
view. You can check out the Github repo at this moment to get an idea of how the project structure looks like.
There are two views for the URL shortener app:
- Home view: This shows the shortener form, and the new URL if the form has already been submitted.
- Redirect view: This redirects to the long URL and adds 1 to the times followed.
Let’s start with the home view which is the most complex. You will need to import the Shortener model and form. You still using a function since I want you to understand all the data flow of the view. Also, you’ll be using the path for a template (Which is not created yet).
Home view
'''
Shortener views
'''
from django.shortcuts import render # We will use it later
from django.http import HttpResponse, Http404, HttpResponseRedirect
# Model
from .models import Shortener
# Custom form
from .forms import ShortenerForm
# Create your views here.
def home_view(request):
template = 'urlshortener/home.html'
context = {}
# Empty form
context['form'] = ShortenerForm()
if request.method == 'GET':
return render(request, template, context)
elif request.method == 'POST':
used_form = ShortenerForm(request.POST)
if used_form.is_valid():
shortened_object = used_form.save()
new_url = request.build_absolute_uri('/') + shortened_object.short_url
long_url = shortened_object.long_url
context['new_url'] = new_url
context['long_url'] = long_url
return render(request, template, context)
context['errors'] = used_form.errors
return render(request, template, context)
The view is based on two conditionals:
- When the HTTP method equals GET: We only pass as context, the Shortener form used to create Shortener objects.
- When the HTTP method equals POST: We still passing the form in the context since we want the user to be able to enter another URL. But we are passing the Post request to another form called used_form.
A tricky way of getting the complete site URL dynamically is by using the request object method build_absolute_uri
.
>>> print(request.build_absolute_uri('/'))
'https://localhost:8080/'
As a safe way of processing a wrong request (The user didn’t enter a valid URL), we get the form errors, pass them as context and render the template as normal. Later you’ll see how to implement the error display in the template.
Redirect view
The redirect_url_view
, is a little bit simpler. It is a detailed view which means, the view only works with an object.
This function takes as parameters, the user’s request and the shortened_part of the URL. It isn’t necessary to assert the type of request we are getting, since we are not working with forms in this view.
def redirect_url_view(request, shortened_part):
try:
shortener = Shortener.objects.get(short_url=shortened_part)
shortener.times_followed += 1
shortener.save()
return HttpResponseRedirect(shortener.long_url)
except:
raise Http404('Sorry this link is broken :(')
We protect the view with a try/except statement, in case the shortened part isn’t found in the database. If the object is found it adds 1 to the times_followed
field and redirects with the HttpResponseRedirect
function to the site URL corresponding to the random code.
Updating URLs
Once you created the two views of the app, it’s time to create the final URL patterns by including the path to the redirect_url_view
.
As always you start by importing the views, and then creating a path function and passing as arguments:
- The URL route
- The view pointing to the path
- The name of the path
'''
Urls for shortener app urlshortener/urls.py
'''
from django.urls import path
# Import the home view
from .views import home_view, redirect_url_view
appname = "shortener"
urlpatterns = [
# Home view
path('', home_view, name='home'),
path('<str:shortened_part>', redirect_url_view, name='redirect'),
]
With this URL setup, the routing of the app looks like this.
localhost:8000/
: Home viewlocalhost:8000/URL-code
: Redirection to the long URL
Creating templates
You are almost there. The only thing that separates you from having built this app is the user interface. For that, we use Django templates.
Templates are used to render a clean interface to the app user. These files are created inside of the app in a double folder structure of the form: “templates/appname”
# urlshortener directory
mkdir -p templates/urlshortener/
Note: The double folder structure and the Django Template language are beyond the scope of this tutorial but you can read about them in the official documentation.
Base template
Django allows template inheritance. This means that we can have a base template and extend it with the purpose of following the DRY (Don’t repeat yourself) principle.
cd templates/urlshortener
touch base.html
The base.html file is a convention and means that every other template on the app, must be an extension of this one.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Django Url shortener</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w=="
crossorigin="anonymous"
/>
</head>
<body>
{% block body %}
{% endblock body %}
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8"
crossorigin="anonymous"
></script>
</body>
</html>
We are going to use the bootstrap CDN, to make a quick and pretty interface without needing CSS or javascript files.
If you want to learn deeply about DTL tags, you can do it with the official docs.
Home template
The home template, inherence from the base.html file. That means that this template includes all the HTML from the parent file.
{% extends 'urlshortener/base.html' %}
{% block body %}
<div class="container">
<div class="card mt-5">
<div class="card-header text-center py-3">
<h1>URL Shortner Application <i class="fas fa-link px-2"></i></h1>
</div>
<div class="px-3 py-4">
<form action="" method="POST">
{% csrf_token %}
<div class="row g-1">
<div class="col-10">{{form.long_url}}</div>
<div class="col-2">
<button class="btn btn-success btn-lg w-100" type="submit">
Shorten
</button>
</div>
</div>
</form>
</div>
</div>
{% if errors %}
<div class="alert alert-danger mt-4" role="alert">
<p>{{errors}}</p>
</div>
{% endif %}
{% if new_url %}
<div class="mx-auto text-center mt-5">
<h2 class="text-danger">Your shortened Url</h2>
<p>You can copy the Link below and share it with your friends</p>
<p class="">{{new_url}}</p>
<p><span class="text-danger">Previous URL:</span> {{long_url}}</p>
</div>
{% endif %}
</div>
{% endblock body %}
I’ll quickly explain the data flow of this template:
- The Shortener form is displayed. Inside, the crsf token is set (Security reasons), and only the long URL field of the form is shown. Remember that this field has the CSS class, “form-control form-control-lg”, because we set that in the form.
- If any errors are found, then show them
- If the POST operation succeeds then, the new URL is shown.
Final application
Congratulations! 🎉. You’ve built a complete functional URL shortener app with Django.
Here are some screenshots of how the application looks like.
Get only:
Error shortening URL:
Successful shortened URL:
URL redirected:
What do you think of exposing a demo of this URL shortener app to someone? Check how to expose a Django Demo app to the internet.
Challenge 🔥
If you feel comfortable with your Django skills, why not practice with a challenge ?.
Clone the code of this application and create an authentication system, where only registered users can shorten their URLs.
When you are done, send a pull request and ping me on Twitter to showcase your achievements.
Wrapping up
You’ve reached the end of this tutorial. Believe it or not, you just reviewed all the main aspects of creating a Django project. I hope you found it extremely useful.
In this tutorial you:
- Learned about the workflow of creating a Django app.
- Built a resume project
- Understood the differences and the structure of Django projects and Django apps.
- Discovered the MVT pattern.
- Created Function base views
- Used the Django ORM (Object Relational Mapper) to create simple models
That’s all, there is a lot to cover with the Django web framework, so keep in touch for more amazing tutorials.