Django Hashid Field
django-hashid-field is a Django library that provides model fields for obfuscating database primary keys using Hashids. It converts integer IDs into short, unique, non-sequential string identifiers, enhancing data privacy and preventing enumeration attacks. The current version is 3.4.1 and it generally follows a release cadence tied to Django LTS releases and feature enhancements.
Common errors
-
django.core.exceptions.ImproperlyConfigured: HASHID_FIELD_SALT must be set in settings.py
cause The required `HASHID_FIELD_SALT` setting is missing from your Django project's `settings.py`.fixAdd `HASHID_FIELD_SALT = 'your-unique-secret-salt'` to your `settings.py`. -
TypeError: Field 'id' expected a number but got 'abCdef'
cause Attempting to query a `HashidAutoField` or `HashidField` using its hashid string when `HASHID_FIELD_ALLOW_INT_LOOKUP` is `False`.fixTo query by hashid, use the appropriate field lookup: `MyModel.objects.get(pk='abCdef')` for primary keys, or `MyModel.objects.get(hashid_field_name='abCdef')` for non-primary key `HashidField`s. Alternatively, set `HASHID_FIELD_ALLOW_INT_LOOKUP = True` in `settings.py` to allow both integer and hashid lookups. -
AttributeError: module 'hashid_field' has no attribute 'HashidPrimaryKey'
cause You are trying to import or use the old field name `HashidPrimaryKey` which was renamed in version 2.0.0.fixReplace `HashidPrimaryKey` with `HashidAutoField`. Update your imports to `from hashid_field import HashidAutoField`.
Warnings
- breaking Version 3.0.0 dropped support for older Django (< 3.2) and Python (< 3.8) versions. The `hashid_field.rest` module was removed and moved to a separate library, `django-hashid-rest-framework`.
- breaking Version 2.0.0 renamed `HashidPrimaryKey` to `HashidAutoField`. It also changed default `salt` and `alphabet` settings, which could result in different hashids being generated for the same integer IDs if you are upgrading and relied on previous defaults.
- gotcha The `HASHID_FIELD_SALT` setting is mandatory. The library will raise an `ImproperlyConfigured` exception if it is not set in your Django settings.
- gotcha By default, querying objects directly by their underlying integer ID is disabled when using `HashidAutoField` or `HashidField` for primary keys. Attempts to do so will result in a `TypeError` or `ValueError`.
Install
-
pip install django-hashid-field
Imports
- HashidField
from hashid_field import HashidField
- HashidAutoField
from hashid_field import HashidPrimaryKey
from hashid_field import HashidAutoField
- HashidBigAutoField
from hashid_field import HashidBigAutoField
Quickstart
import os
from django.db import models
from django.conf import settings
from hashid_field import HashidField, HashidAutoField
# Minimal Django settings for the field to work
if not settings.configured:
settings.configure(
DEBUG=True,
SECRET_KEY='a-very-secret-key',
INSTALLED_APPS=['django_hashid_field_test'],
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
HASHID_FIELD_SALT=os.environ.get('DJANGO_HASHID_FIELD_SALT', 'default-test-salt'),
HASHID_FIELD_ALLOW_INT_LOOKUP=True
)
class Product(models.Model):
# Use HashidAutoField for a primary key (replaces integer ID)
id = HashidAutoField(primary_key=True)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
# Use HashidField for a non-primary key field
# It stores an integer in the DB, but exposes a hashid string
legacy_id = HashidField(null=True, blank=True)
def __str__(self):
return f"{self.name} ({self.id})"
# Example usage (after makemigrations and migrate)
# from django.db import connection
# from django.apps import apps
# apps.populate(settings.INSTALLED_APPS)
# with connection.schema_editor() as schema_editor:
# schema_editor.create_model(Product)
#
# product = Product.objects.create(name="Test Product", price=99.99, legacy_id=12345)
# print(f"Created Product: {product.name}, Hashid ID: {product.id}, Legacy Hashid: {product.legacy_id}")
#
# # Look up by hashid string
# retrieved_product = Product.objects.get(id=product.id)
# print(f"Retrieved Product by hashid: {retrieved_product.name}")
#
# # Look up by underlying integer ID (if HASHID_FIELD_ALLOW_INT_LOOKUP is True)
# retrieved_product_int = Product.objects.get(id=product.id.id)
# print(f"Retrieved Product by int ID: {retrieved_product_int.name}")