How I Created an URL Shortener App Using Django

5/5 - (6 votes)

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