DEV Community

Shen
Shen

Posted on • Originally published at shenli.dev

1

Seamlessly integrate Hashids with Django

Introduction

Hashids is a library that maps an integer to a string with provided salt, alphabet, and min_length. For example, it can turn integer 1 into "r87f" and convert it back when required.

It is a way to obfuscate ids and is particularly useful when you don't want anyone to iterate through everything in your database by going through all the ids.

I have used Hashids a few times with Django. And every time I need to expose the id field, I would convert the id value to a hashids by calling something like this:

from utils import hashids

def to_json(obj):
    exposed_id = hashids.encode(obj.id)
    ...
    return {
        'id': exposed_id,
        ...
    }

And everywhere that exposed_id is used I need to convert it back, which ended up as a lot of code in different places.

Another issue with this approach is that it's hard to use different configurations, such as salt and alphabet, for different models. The exposed_id for different models with the same actual id will be the same.

There are some existing projects that integrate the two, but they are more intrusive than I would like them to be. As they usually actually writes to the database, instead of just encode/decode between obfuscated id and integer ids on the fly.

This leads to this small library I made with less than 100 lines of code, called django-hashids.

django-hashids

django-hashids integrates Django with Hashids by introducing a "virtual" field to Django models. It is "virtual" because it does not have a column in the database but allows people to query it as if there were an actual database column.

Here's a simple example:

class TestModel(Model):
    hashid = HashidsField(real_field_name="id")

instance = TestModel.objects.create()
instance2 = TestModel.objects.create()
instance.id  # 1
instance2.id  # 2

# Allows access to the field
instance.hashid  # '1Z'
instance2.hashid  # '4x'

# Allows querying by the field
TestModel.objects.get(hashid="1Z")
TestModel.objects.filter(hashid="1Z")
TestModel.objects.filter(hashid__in=["1Z", "4x"])
TestModel.objects.filter(hashid__gt="1Z")  # same as id__gt=1, would return instance 2

# Allows usage in queryset.values
TestModel.objects.values_list("hashid", flat=True) # ["1Z", "4x"]
TestModel.objects.filter(hashid__in=TestModel.objects.values("hashid"))

As you can see, it allows you to use TestModel.hashid like a real field with all the sensible lookups but queries are proxies to id field with encoding/decoding happening on the fly providing a seamless experience.

For more usage and configuration options please visit django-hashids on github

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay