Django is a Python framework used for building awesome web applications. In this tutorial, I’ll show you how I created a URL shortener application using Django. This is one of the best beginner projects to sharpen your skills as a Django developer.
You can download the whole project at the end of this article. 👇
Just as the name implies, an URL shortener helps to shorten long URLs making them easy to remember and manage. Examples of websites that offer URL shortener services include bit.ly and ow.ly.
Twitter integrates such techniques right into its product. This makes it easy for users to share long URLs in a Tweet without getting passed the maximum number of characters required.
Getting Started

I suggest you familiarize yourself with Django and how it is used in Python for web development to fully understand what we are doing in this tutorial. Basic knowledge of the command line, though not required, will be an added advantage.
I will try my best to explain all that we will be doing in this project. If you still do not understand, you have the internet at your disposal.
Let’s start by creating a directory for this project. For your information, I am using an Ubuntu terminal.
mkdir django_project && cd django_project
We created and changed to the django_project
directory. We are going to follow best practices by creating a virtual environment.
python3 -m venv .venv
The above command creates a virtual environment with the name .venv
. If it doesn’t work for you, just remember there are many ways to kill a rat.
Try this alternative:
pip install virtualenv python3 -m virtualenv .venv
Activate the virtual environment using either the source command or dot.
source .venv/bin/activate . .venv/bin/activate
Type ‘deactivate
’ to deactivate the virtual environment. Make sure you are in a virtual environment throughout this tutorial unless you enjoy getting lost in the Sahara. Then, install the Django library using pip
.
pip install django
After installation, it is important to create a requirements.txt
file to keep all the Python packages and dependencies for this project.
pip freeze > requirements.txt
This will be used in the future by anyone trying to implement this project especially if a new version is released that impacts the smooth running of this project.
Creating a Django Project

Now let’s get our hands dirty by building a URL shortener application. First thing first, create a Django project. Make sure you are in the django_project
directory.
django-admin startproject Shorty .
Have you ever killed two birds with one stone? I know you will say, ‘that’s not possible.’ Guess what, you just did it! Not just two, not three, but five all with one command! What is more, you are going to do it again.
💡 Explanation: Shorty is the name of our Django project. The dot before the space tells Django to create the project in our current directory. Otherwise, it will create a folder to house the heavy-duty files, and you will be jumping from one room to another just to locate the files.
Because I won’t have the time to call a paramedic when you finally bump your head, let’s just keep Shorty in our current directory. Note that this is optional but it’s considered best practice.
Use the ls command to glance through Shorty.
ls Shorty __init__.py asgi.py settings.py urls.py wsgi.py
This is what we see in a typical Django project folder. The __init__.py
is there to tell Python to handle Shorty with care and treat it as a Python package so that it can be imported into another Python file.
The urls.py
is where we will add the URLs of any app created, and by doing this, we are telling Django to build a particular page in the app in response to a browser request.
The settings.py
is self-explanatory. It controls our project’s settings. This includes any app created, the database to be used, and additional files added to the current directory.

These are the files that concern what we are doing. Was it my fault that you caught five instead of three? 🐍
If five is your favorite number, then see how I woke up a sleeping giant from its centuries-long slumber by giving it five “senses” that kept it awake forever.
Notice the manage.py
file in our current directory. It is executable by default and can execute you if you come near, as shown by the green color. Just relax, and have no fear, it is only used to execute various Django commands, some of which we are going to see in this tutorial.
Another thing to add is this: if you are not aware, Django has a built-in web server that allows us to see what we have built, and make changes or corrections before taking it to a production server. So, the server is for local development purposes.
Let’s start the server to see if the installations were successful.
python3 manage.py runserver

If you see something similar to the above image, you know that everything went successfully.
Creating an Application

Let’s now create a URL shortener app for our project and call it urlshortener
.
python3 manage.py startapp urlshortener
Let’s take a peek using the ls
command once again.
ls urlshortener __init__.py admin.py apps.py migrations models.py tests.py views.py
It seems that this stone carries more weight than the former, as it was able to hit more than five at once.
The admin.py
configures the built-in Django admin application. The migration file keeps track of any changes in our models.py
. The models.py
is where we define our database model. The views.py
is where we define how we want our web app to be displayed in a browser. It handles the request/response logic for our app.
The tests.py
is for carrying out tests to ensure that everything is working as expected. We will not carry out tests in this tutorial, probably because we are not with our testing equipment. Remember, I said, probably.
Do you know that Django is not even aware that we have created a new app? That’s because we have neither made a phone call to Django nor paid a visit to his royal palace via the settings.py
. To make Django aware, open the settings.py
file.
Wait! Why the rush? This room we are about to enter is not a scattered area. Everything is organized and neatly kept. So, make sure you sanitize yourself very well to avoid introducing bugs there. Don’t touch any needle or thread you see there because you don’t know which one is holding the foundation of this building. Quietly enter, do what you want to do, and leave immediately.
nano Shorty/settings.py
Scroll down to where you will see ‘INSTALLED_APPS
.’ Then, add the app we just created.
The MVT Pattern

Django developers create web applications using the Model View Template pattern. The model shows how we want our data stored in the database to behave. The view is what we see when we open a website from our browser. It is used to interact with a model and to execute the business logic.
By business logic, we are not talking about buying and selling. we meant the way data is created, stored, and deleted. Templates are the HTML files that are returned and displayed by the view to the user upon request.
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # custom app 'urlshortener', ]
The Model
Following the MVT pattern, we will start by creating a database model. Open your model.py
file in the app folder.
nano urlshortener/models.py
from django.db import models # Create your models here. class ModelData(models.Model): old_url = models.CharField(max_length=200) short_url = models.CharField(max_length=15, unique=True) def __str__(self): return f'Short URL for: {self.old_url} is {self.short_url}'
We created a simple model for beginners to understand. From the file, we imported the models class which contains all we need to create a Django model. The name of our database model is ModelData
. In it, we extend the models.Model
. ModelData
is now a subclass of models.Model
. This is a compulsory requirement for any Django app, that is, for all models to extend the models.Model
.
old_url
and short_url
are the fields we want the model to have on the database.
They referred to the old and the newly generated shortened URL respectively. We specified the type of content they will hold, CharField()
. This is one of the many model fields Django supports. We also specified the number of characters needed. By specifying unique=True
, we don’t want to have repeated shortened URLs.
The __str__
method is simply showing how the model should be printed. If everything isn’t clear, take your time and read this subheading again. You may wish to learn more about object-oriented programming.
💡 Recommended: Object-Oriented Programming

Next, we create a file in the app folder named utils.py
where we will create a function that will generate a random code.
nano urlshortener/utils.py
from django.conf import settings from random import choice from string import ascii_letters, digits SIZE = getattr(settings, 'MAXIMUM_URL_CHARS', 7) CHARS = ascii_letters + digits def random_code(chars=CHARS): return ''.join([choice(chars) for _ in range(SIZE)]) def unique_code(instance): shorten_code = random_code() model_class = instance.__class__ if model_class.objects.filter(short_url=shorten_code).exists(): return unique_code(instance) return shorten_code
We imported the choice
method from the random
module.
💡 Recommended: Python’s Random Module – Everything You Need to Know to Get Started
We also imported the alphabetical and numerical characters from the string
module. We then used the plus operator +
to concatenate the characters and save them in a variable. The variable then becomes a parameter of the random_code()
function.
In the function, we performed a list comprehension that loop over the alphanumeric characters and generate random characters of the specified length of the settings.py
file, or 7 by default. This will become the shortened URL.
We use the function getattr
to get a variable from the settings.py
file and to avoid throwing an error if the variable is not specified.
We created another function to ensure unique random code is generated.
Let’s go back to the models.py
file and add a save method to save the random code to the database.
from django.db import models from .utils import unique_code class ModelData(models.Model): … def save(self, *args, **kwargs): if not self.short_url: self.short_url = unique_code(self) super().save(*args, **kwargs)
Notice that the random code is saved in the short_url
field if it isn’t specified. Let’s now activate our model. In Ubuntu terminal, run the following commands:
python3 manage.py makemigrations urlshortener python3 mange.py migrate
The makemigration
command does not execute commands on the database file. It only tells Django that changes have been made to the database model. The migrate
command which builds the actual database does the execution.
Creating Forms

Forms enable Django to get input from the user. We will create a form in a file called forms.py
. Whenever you create a form, ensure you use the same file name.
nano urlshortener/forms.py
from django import forms from .models import ModelData class ModelDataForm(forms.ModelForm): widget = forms.URLInput(attrs={'class': 'form-control form-control lg', 'placeholder': 'Enter your URL here ...'}) url = forms.URLField(widget=widget) class Meta: model = ModelData fields = {'url',}
We want to create a model object from user input so we imported the form.ModelForm
class. Thus, the ModelDataForm
becomes a subclass of forms.ModelForm
. In the widget variable, we specified a CSS class attribute because we will use bootstrap to style our web page.
Views/Templates/URLs
We want to wire up our views, templates, and URLs to display our database content on our homepage. Starting with views, open views.py
. We are going to create two functions.
nano urlshortener/views.py
from django.shortcuts import render, redirect from .forms import ModelDataForm from django.http import HttpResponseRedirect, Http404 # Create your views here. def home_view(request): template = 'home.html' if request.method == 'GET': context = {'form': ModelDataForm()} return render(request, template, context) if request.method == 'POST': form = ModelDataForm(request.POST) if form.is_valid(): shortener = form.save() old_url = shortener.old_url new_url = request.build_absolute_uri('/') + shortener.short_url context = {'old_url': old_url, 'new_url': new_url} return render(request, template, context) context = {'errors': form.errors} return render(request, template, context)
A lot is going on here.
We first imported the render()
and redirect()
functions as well as the ModelDataForm
class.
- If the request method is GET, an empty form is displayed.
- If it is POST, we also check to ensure the input gotten from the user is a valid URL.
If so, we saved it to the ModelData
(because we use the forms.Model
in the ModelDataForm
class).
Since it is saved in the database model, we can now retrieve them using the respective model field, and save them in a variable in dictionary form.
The build_absolute_uri()
method is a nice way of getting the home URL. When added together with the random code, it becomes the new URL.
If the request method is neither GET nor POST, an error is displayed.
def redirect_view(request, shortened): try: data = ModelData.objects.get(short_url=shortened) data.save() return HttpResponseRedirect(data.url) except: raise Http404('Sorry, the link is not valid')
I believe this function is self-explanatory. If the shortened part is not found in the database, an error is raised.
Let’s update our urls.py
. first starting with the app-level URL. Create a file called urls.py
.
from django.urls import path from .views import home_view, redirect_view urlpatterns = [ path('', home_view, name='home'), path('<str:shortened>', redirect_view, name='redirect'), ]
Next is the project-level urls.py
file.
nano Shorty/urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('urlshortener.urls')), ]
The path()
function returns an element to be included in the urlpatterns
. Using the variable, urlpatterns
is a Django requirement. So make sure it is accurately spelled. The include()
function registers the app-level URL.
I’m sure by now you are already sweating like a Christmas goat. That should be expected if you are a beginner. Don’t worry, you will soon get used to it with constant practice. If you have some knowledge already, you will notice that I made this fairly simple as there are other things I didn’t include. It’s because I made this project with beginners in mind.
However, it is not over until it is over. Next to be taken care of in the MVT pattern are templates.
Templates

We will create all our HTML files here including the base.html
which other files will inherit from. First of all, we create the templates folder in our current directory and let Django know of this in the TEMPLATES section of the settings.py
file.
mkdir templates
settings.py
TEMPLATES = [ { … 'DIRS': [os.path.join(BASE_DIR, 'templates')], … }
Make sure you import the os
module. Inside the templates
folder, create a file named base.html
and copy and paste the following code.
<!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 making use of bootstrap CDN to quickly style our webpage. Create another file, the home.html
that will inherit everything in the base.html
. Then, copy and paste the following code.
{% extends '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.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> <a href="{% url 'home' %}">Home</a> <p>Your URL has been shortened, thank you.</p> <p class="">{{new_url}}</p> </div> {% endif %} </div> {% endblock body %}
We will make do with just a brief explanation of the HTML files. The {% … %) and {{ … }}
syntaxes are Django’s templating language. It is used to perform a for
loop, and conditional statements and to dynamically write to webpages. We used extends
to inherit everything in the base.html
file. The csrf_token
is for security reasons. It is a compulsory requirement when creating a form.

The form.url
is the URL field from the forms.py
file. The error and new_url
are from the context variable in the views.py
file.
Conclusion

Congratulations, you have created a URL shortener application using Django. You are to be commended if you have been with me so far in this tutorial. We are going to pop the champagne! You have undoubtedly learned a thing or two you can apply while building webpages with Django.
Feel free to download the whole project code from GitHub.
If you feel overwhelmed, that should be expected. With time, you will get used to it. The key is consistency. Keep building projects and in no distant time, you will become a full-stack Django developer.
💡 Recommended: Full-Stack Web Developer — Income and Opportunity

Jonathan Okah is a professional freelance Python developer whose primary goal is to solve a perceived problem with the use of technology, thus making life easy for you.
He has basic knowledge of Python, C, JavaScript, HTML, CSS and R, but has taken his knowledge of Python to the next level at Upwork, where he performs services for clients.
Jonathan has a flair for writing and has written several blog articles for clients. He is constantly learning to improve his skills. Currently, he is studying software engineering, hoping to become a full-stack web developer.
In his leisure time, Jonathan enjoys playing games and watching the English Premier League. You can contact him for your freelance projects at Upwork.