Building a Simple Link Shortener for Your Django App
Tags: python
Published: 2019-06-09 20:37:22

As part of my work on [slideZing](https://slidezing.com), I wanted to include a simple link shortener so that it was easier to share really long urls. This article will walk through why you might want to do this and how to implement a relatively simple one into your existing django app.

For this article, I assume that you know the basics of how to create a django application and that you would like to include this shortener in your existing application.

## Why shorten a link?

Django applications frequently use a slug field in the model definitions. A slug is simply a short name that is used in a url to give it a human friendly name. Refer to the official documentation for more information on how to use the [slug field](https://docs.djangoproject.com/en/2.2/ref/models/fields/#slugfield).

For example, if you are building a blog, you might have a title field for the blog but you also want the description in the url to uniquely identify the blog post.

For a post, my slug might be "intro-to-django-class-based-views" which
would lead to a full url that looks something like this:
http://acoolblog.com/blog/intro-to-django-class-based-views

This approach is good because the url is descriptive and tells us a little about the blog post without even reading it. The downside of this approach is that it is a really long url. When sharing via twitter or other channels, shorter can be better.

One common approach is to use a 3rd party service such as bitly or tinyurl to translate that long link to something more compact. There is nothing wrong with this approach. However, I want to increase brand awareness so I am keeping all urls going through the same app. Since it is relatively easy to do, why not give it a shot?

I will also give some additional caveats. This link shortening approach does not take any arbitrary url and shorten it. It is based on a unique id associated with each django object. This means that if you just want short links for blog posts, this will work great. If you want short links for images, comments and other pages, you will need to modify this approach.

The other caveat I want to make is that this is not appropriate to use for any type of security. It is very easy to run this algorithm and try to "guess" urls. I am assuming that these links are all public and that any attempts to guess short urls are not harmful to your site.

## Start the code

This code is heavily based on [django short-urls](https://github.com/bfirsh/django-shorturls) but is trimmed down quite a bit for easy integration into your app.

For the actual shortening algorithm, we will use a python module called [short_url](https://pypi.org/project/short_url/).

Please make sure to install it using pip:

```
pip install short_url
```

It is worth trying it out so you can see what it does:

```
import short_url
print(short_url.encode_url(2))

25t25

print(short_url.encode_url(3))

ghpzy
```

At its simplest level, short_url is using an algorithm to convert any number into a sequence of letters. The algorithm is deterministic which means every time you encode the value 2, you get the same result back.

Since the algorithm always calculates the same result, we can also decode a text string and get a number:

```
print(short_url.decode_url('25t52'))

2

print(short_url.decode_url('ghpzy'))

3
```

One other nice aspect of this algorithm is that certain letters and numbers are excluded so people will not get confused about whether it is 0 or a O, for example.

Now that we know the basics for create a unique id, we can incorporate this into our existing django app with a little additional work.

### Integrating with Django

I am going to create a `shorturl` app that includes a urls.py, views.py files.

Here is the example urls.py file in my shorturl app:

```
from django.urls import path

from .views import redirect_view

urlpatterns = [path('<slug:tiny>/', view=redirect_view, name='short_url_view')]

```


This file basically is telling django that when a url comes in that looks
like this: /25t52/ to call the redirect_view and pass in the value 25t52
to the view function.

I also include this line in my master urls.py file:

```
path('short/', include('shorturl.urls'))
```

This makes sure that the url will look something like this:
http://acoolblog.com/short/25t52/

Now that we have the url structure set up, let's take a look at the actual
views.py function:

```
from django.apps import apps
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
import short_url


def redirect_view(request, tiny):
    """
    Redirect to a given object from a short URL.
    """

    model = apps.get_model('MYAPP', 'MYBLOG')
    try:
        id = short_url.decode_url(tiny)
    except ValueError:
        raise Http404('Bad encoded ID.')

    # Try to look up the object. If it's not a valid object, or if it doesn't
    # have an absolute url, bail again.
    obj = get_object_or_404(model, pk=id)
    return redirect(obj)

```

In this example, I have an app called MYAPP and a blog model called MYBLOG. Make sure to modify this to use your own app and models.

The code attempts to convert the tiny string into a number. If this conversion is unsuccessful, it will return an HTTP 404 error.

The final step is to figure out what object corresponds to the ID by using
the `get_object_or_404` function. Once it finds the object, it will redirect to a url that shows that object.

The question is now, how does it know what url to use for that object?

By using the `get_absolute_url` function in our model.

In this case, if we have a MYBLOG model, we can include a `get_absolute_url` function in the model definition that looks like this in our models.py file:

```
def get_absolute_url(self):
    return reverse_lazy('blog_page', args=[self.slug])

```

Please refer to the django documentation on [get_absolute_url](https://docs.djangoproject.com/en/2.2/ref/models/instances/#get-absolute-url) if you are not comfortable with this code and where to include it.

We are almost done!

The final piece we need to include is a way to generate a short_url. For my implementation, I decided to add it to the models.py
file right below my `get_absolute_url` function:

```
def get_short_url(self):
    tinyid = short_url.encode_url(self.pk)
    return reverse_lazy('short_url_view', kwargs={'tiny': tinyid})
```

This portion of code will take the object's id and create a url based on it and return it.

Now we know how to generate a short url and turn it into a longer one. The final step is to include the information in a template. If we want to build a full shareable url, we can include this information in our template:

```
<a href="{{ request.scheme }}://{{ request.get_host }}{{ MYBLOG.get_short_url }}">

```

This portion of code will return the full url, that would be suitable for
sharing via twitter: http://acoolblog.com/short/25t52/

Finally, do not forget to add this `shorturl` app to your settings.py file in the `DJANGO_APPS` list. 

To share an example of this in action, I used slideZing to share my slides from a recent talk. The short url looks like this [https://slidezing.com/short/4xct4/](https://slidezing.com/short/4xct4/) and then redirects to [https://slidezing.com/landing/chrism/evangelizing-python-for-business/](https://slidezing.com/landing/chrism/evangelizing-python-for-business/)

I hope this is helpful to everyone. Feel free to leave comments if you have questions about the approach.