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

Top comments (0)

Feature flag article image

Create a feature flag in your IDE in 5 minutes with LaunchDarkly’s MCP server 🏁

How to create, evaluate, and modify flags from within your IDE or AI client using natural language with LaunchDarkly's new MCP server. Follow along with this tutorial for step by step instructions.

Read full post

👋 Kindness is contagious

Discover fresh viewpoints in this insightful post, supported by our vibrant DEV Community. Every developer’s experience matters—add your thoughts and help us grow together.

A simple “thank you” can uplift the author and spark new discussions—leave yours below!

On DEV, knowledge-sharing connects us and drives innovation. Found this useful? A quick note of appreciation makes a real impact.

Okay