Package: django-hashid-field
Django Hashid FieldA custom Model Field that uses theHashidslibraryto obfuscate an IntegerField or AutoField. It can be used in new models or dropped in place of an existing IntegerField,
explicit AutoField, or an automatically generated AutoField.FeaturesStores IDs as integers in the databaseAllows lookups and filtering by hashid string or Hashid object and (optionally) integer.Can enable integer lookups globally or per-fieldCan be used as sort keyAllows specifying a salt, min_length and alphabet globallySupports customsalt,min_length,alphabet,prefixandallow_int_lookupsettings per fieldAllows prefixing hashids with custom string, e.g.prefix=”user_”for hashids like “user_h6ks82g”Can drop-in replace an existing IntegerField (HashidField) or AutoField (HashidAutoField)Supports “Big” variants for large integers: BigHashidField, BigHashidAutoFieldSupports Django 3.2 settingDEFAULT_AUTO_FIELD = ‘hashid_field.BigHashidAutoField’Supports Django REST Framework SerializersSupports exact ID searches in Django Admin when field is specified in search_fields.Supports common filtering lookups, such as__iexact,__contains,__icontains, though matching is the same as__exact.Supports subquery lookups withfield__in=querysetSupports other lookups:isnull,gt,gte,ltandlte.Supports hashing operations so the fields can be used in Dictionaries and Sets.RequirementsThis module is tested and known to work with:Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12Django 3.2, 4.2, 5.0Hashids 1.3Django REST Framework 3.14Please Note: Python 2.x is at its end of life and is no longer supported.InstallationInstall the package (preferably in a virtualenv):$pipinstalldjango-hashid-fieldConfigure a global SALT for all HashidFields to use by default in your settings.py. (Note: Using a global salt for all
fields will result in IDs from different fields/models being the same. If you want to have unique hashid strings for the
same id, then also configure per-field salts as described in Field Parameters below.)HASHID_FIELD_SALT="a long and secure salt value that is not the same as SECRET_KEY"# Note: You can generate a secure key with:#     from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())Add it to your modelfromhashid_fieldimportHashidFieldclassBook(models.Model):reference_id=HashidField()Migrate your database$./manage.pymakemigrations$./manage.pymigrateBasic UsageUse your field like you would any other, for the most part. You can assign integers:>>>b=Book()>>>b.reference_id=123>>>b.reference_idHashid(123):OwLxW8DYou can assign valid hashids. It’s valid only if it can be decoded into an integer based on your settings:>>>b.reference_id='r8636LO'>>>b.reference_idHashid(456):r8636LOYou can access your field with either hashid strings or Hashid objects:>>>Book.objects.filter(reference_id='OwLxW8D')<QuerySet[<Book:(OwLxW8D)>]>>>>b=Book.objects.get(reference_id='OwLxW8D')>>>b<Book:(OwLxW8D)>>>>h=b.reference_id>>>hHashid(123):OwLxW8D>>>Book.objects.filter(reference_id=h)<Book:(OwLxW8D)>You can lookup objects with integers if you setHASHID_FIELD_ALLOW_INT_LOOKUP = Trueorallow_int_lookup=Trueas a parameter to the field.reference_id=HashidField(allow_int_lookup=True)Now integer lookups are allowed. Useful if migrating an existing AutoField to a HashidAutoField, but you need to allow
lookups with older integers.>>>Book.objects.filter(reference_id=123)<QuerySet[<Book:(OwLxW8D)>]>By default, the objects returned from a HashidField are an instance of the class Hashid (this can be disabled globally
or per-field), and allow basic access to the original integer or the hashid:>>>fromhashid_fieldimportHashid>>>h=Hashid(123)>>>h.id123>>>h.hashid'Mj3'>>>print(h)Mj3>>>repr(h)'Hashid(123): Mj3'Hashid Auto FieldAlong withHashidFieldthere is also aHashidAutoFieldthat works in the same way, but that auto-increments just
like anAutoField.fromhashid_fieldimportHashidAutoFieldclassBook(models.Model):serial_id=HashidAutoField(primary_key=True)The only difference is that if you don’t assign a value to it when you save, it will auto-generate a value from your
database, just as an AutoField would do. Please note thatHashidAutoFieldinherits fromAutoFieldand there can
only be oneAutoFieldon a model at a time.>>>b=Book()>>>b.save()>>>b.serial_idHashid(1):AJEM7LKIt can be dropped into an existing model that has an auto-created AutoField (all models do by default) as long as you
give it the same name and setprimary_key=True. So if you have this model:classAuthor(models.Model):name=models.CharField(max_length=40)Then Django has created a field for you called ‘id’ automatically. We just need to override that by specifying our own
field withprimary_keyset to True.classAuthor(models.Model):id=HashidAutoField(primary_key=True)name=models.CharField(max_length=40)And now you can use the ‘id’ or ‘pk’ attributes on your model instances:>>>a=Author.objects.create(name="John Doe")>>>a.idHashid(60):N8VNa8z>>>Author.objects.get(pk='N8VNa8z')<Author:Authorobject>In Django 3.2 a new setting, “DEFAULT_AUTO_FIELD” was added to change all auto-generated AutoFields to a specific class.
This is fully supported with django-hashid-field, and can be enabled with:DEFAULT_AUTO_FIELD='hashid_field.HashidAutoField'DEFAULT_AUTO_FIELD='hashid_field.BigHashidAutoField'Care must be given, as this will alter ALL models in your project. Usually you would only set this in a new project.
Also, since this changes the auto-generated field, only global settings will be used for that field. If you desire
specific settings for different models, then using this setting is not advised.Global SettingsHASHID_FIELD_SALTYou can optionally set a global Salt to be used by all HashFields and HashidAutoFields in your project. Do not use the
same string as your SECRET_KEY, as this could lead to your SECRET_KEY being exposed to an attacker.
Please note that changing this value will cause all HashidFields to change their values, and any previously published
IDs will become invalid.
Can be overridden by the field definition if you desire unique hashid strings for a given field, as described in
Field Parameters below.Type:stringDefault:“”Note:The upstream hashids-python library [only considers the first 43 characters of the salt](https://github.com/davidaurelio/hashids-python/issues/43).Example:HASHID_FIELD_SALT="a long and secure salt value that is not the same as SECRET_KEY"HASHID_FIELD_MIN_LENGTHDefault minimum length for (non-Big) HashidField and AutoHashidField.
It is suggested to use 7 for HashidField and HashidAutoField, so that all possible values
(up to 2147483647) are the same length.Type:integerDefault:7Example:HASHID_FIELD_MIN_LENGTH=20HASHID_FIELD_BIG_MIN_LENGTHDefault minimum length for BigHashidField and BigHashidAutoField.
It is suggested to use 13 for BigHashidField and BigHashidAutoField, so that all possible values
(up to 9223372036854775807) are the same length.Type:integerDefault:13Example:HASHID_FIELD_BIG_MIN_LENGTH=30HASHID_FIELD_ALPHABETThe default alphabet to use for characters in generated Hashids strings. Must be at least 16 unique characters.Type:stringDefault:“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890”Example:HASHID_FIELD_ALPHABET="0123456789abcdef"HASHID_FIELD_ALLOW_INT_LOOKUPAllow lookups or fetches of fields using the underlying integer that’s stored in the database.
Disabled by default to prevent users from being to do a sequential scan of objects by pulling objects by
integers (1, 2, 3) instead of Hashid strings (“Ba9p1AG”, “7V9gk9Z”, “wro12zm”).
Can be overridden by the field definition.Type:booleanDefault:FalseExample:HASHID_FIELD_ALLOW_INT_LOOKUP=TrueHASHID_FIELD_LOOKUP_EXCEPTIONBy default any invalid hashid strings or integer lookups when integer lookups are turned off will result in an
EmptyResultSet being returned. Enable this to instead throw a ValueError exception (similar to the behavior prior to 2.0).Type:booleanDefault:FalseExample:HASHID_FIELD_LOOKUP_EXCEPTION=TrueHASHID_FIELD_ENABLE_HASHID_OBJECTThe default behavior is to return an instance of the Hashid object (described below) in each instance of your Model.
This makes it possible to get both the integer and hashid version of the field. However, other django modules, serializers,
etc may be confused and not know how to handle a Hashid object, so you can turn them off here. Instead, a string
of the hashid will be returned, and a new attribute with the suffix_hashidwill be created on each instance with the
Hashid object. So if you havekey = HashidField(…)thenkey_hashidwill be created on each instance.
Can be overriden by the field definition.Type:booleanDefault:TrueExample:HASHID_FIELD_ENABLE_HASHID_OBJECT=FalseHASHID_FIELD_ENABLE_DESCRIPTORBy default a Hashid*Field on a model will replace the original value returned from the database with a Descriptor
that attempts to convert values that are set on that field of an instance with a new Hashid object (or string if
ENABLE_HASHID_OBJECT is False), regardless if you set an integer or a valid hashid. For the most part this is
completely invisible and benign, however if you have issues due to this descriptor, you can disable it here, or
on the field, and the raw value will not be replaced with the Descriptor.
Can be overriden by the field definition.Type:booleanDefault:TrueExample:HASHID_FIELD_ENABLE_DESCRIPTOR=FalseField ParametersBesides the standard field options, there are settings you can tweak that are specific to HashidField and
AutoHashidField.Please notethat changing any of the values forsalt,min_length,alphabetorprefixwillaffect
the obfuscation of the integers that are stored in the database, and will change what are considered “valid” hashids.
If you have links or URLs that include your HashidField values, then they will stop working after changing any of these
values. It’s highly advised that you don’t change any of these settings once you publish any references to your field.saltLocal overridable salt for hashids generated specifically for this field.
Set this to a unique value for each field if you want the IDs for that field to be different to the same IDs
on another field. e.g. so thatbook.id = Hashid(5): 0Q8Kg9randauthor.id = Hashid(5): kp0eq0V.
Suggestion:fieldname = HashIdField(salt=”modelname_fieldname_” + settings.HASHID_FIELD_SALT)See HASHID_FIELD_SALT above.Type:stringDefault:settings.HASHID_FIELD_SALT, “”Note:The upstream hashids-python library [only considers the first 43 characters of the salt](https://github.com/davidaurelio/hashids-python/issues/43).Example:reference_id=HashidField(salt="Some salt value")min_lengthGenerate hashid strings of this minimum length, regardless of the value of the integer that is being encoded.
This defaults to 7 for the field since the maximum IntegerField value can be encoded in 7 characters with
the defaultalphabetsetting of 62 characters.Type:intDefault:7Example:reference_id=HashidField(min_length=15)alphabetThe set of characters to generate hashids from. Must be at least 16 characters.Type:string of charactersDefault:Hashids.ALPHABET, which is “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890”Example:# Only use numbers and lower-case lettersreference_id=HashidField(alphabet="0123456789abcdefghijklmnopqrstuvwxyz")prefixAn optional string prefix that will be prepended to all generated hashids. Also affects validation, so only hashids
that have this prefix will be considered correct.Type:StringDefault:“”Example:# Including the type of id in the id itself:reference_id=HashidField(prefix="order_")allow_int_lookupLocal field override for default global on whether or not integer lookups for this field should be allowed.
See HASHID_FIELD_ALLOW_INT_LOOKUP above.Type:booleanDefault:settings.HASHID_FIELD_ALLOW_INT_LOOKUP, FalseExample:reference_id=HashidField(allow_int_lookup=True)enable_hashid_objectLocal field override for whether or not to return Hashid objects or plain strings.
Can be safely changed without affecting any existing hashids.
See HASHID_FIELD_ENABLE_HASHID_OBJECT above.Type:booleanDefault:settings.HASHID_FIELD_ENABLE_HASHID_OBJECT, TrueExample:reference_id=HashidField(enable_hashid_object=False)enable_descriptorLocal field override for whether or not to use the Descriptor on instances of the field.
Can be safely changed without affecting any existing hashids.
See HASHID_FIELD_ENABLE_DESCRIPTOR above.Type:booleanDefault:settings.HASHID_FIELD_ENABLE_DESCRIPTOR, TrueExample:reference_id=HashidField(enable_descriptor=False)Hashid ClassOperations with a HashidField or HashidAutoField return aHashidobject (unless disabled).
This simple class does the heavy lifting of converting integers and hashid strings back and forth.
There shouldn’t be any need to instantiate these manually.Methods__init__(value, salt=””, min_length=0, alphabet=Hashids.ALPHABET, prefix=””, hashids=None):value:REQUIREDInteger you wish toencodeor hashid you wish todecodesalt:Salt to use.Default: “” (empty string)min_length:Minimum length of encoded hashid string.Default: 0alphabet:The characters to use in the encoded hashid string.Default: Hashids.ALPHABETprefix:String prefix prepended to hashid strings.Default: “” (empty string)hashids:Instance of hashids.Hashids to use for encoding/decoding instead of instantiating another.Read-Only Propertiesidtype:Intvalue:Thedecodedintegerhashidtype:Stringvalue:Theencodedhashid stringhashidstype:Hashids()value:The instance of the Hashids class that is used toencodeanddecodeprefixtype:Stringvalue:The prefix prepended to hashid stringsDjango REST Framework IntegrationIf you wish to use a HashidField or HashidAutoField with a DRF ModelSerializer, there is one extra step that you must
take. Automatic declaration of any Hashid*Fields will result in an ImproperlyConfigured exception being thrown. You
must explicitly declare them in your Serializer, as there is no way for the generated field to know how to work with
a Hashid*Field, specifically what ‘salt’, ‘min_length’ and ‘alphabet’ to use, and can lead to very difficult errors or
behavior to debug, or in the worst case, corruption of your data. Here is an example:fromrest_frameworkimportserializersfromhashid_field.restimportHashidSerializerCharFieldclassBookSerializer(serializers.ModelSerializer):reference_id=HashidSerializerCharField(source_field='library.Book.reference_id')classMeta:model=Bookfields=('id','reference_id')classAuthorSerializer(serializers.ModelSerializer):id=HashidSerializerCharField(source_field='library.Author.id',read_only=True)classMeta:model=Authorfields=('id','name')Thesource_fieldallows the HashidSerializerCharField to copy the ‘salt’, ‘min_length’ and ‘alphabet’ settings from
the given field atapp_name.model_name.field_nameso that it can be defined in just one place. Explicit settings are
also possible:reference_id=HashidSerializerCharField(salt="a different salt",min_length=10,alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ")If nothing is given, then the field will use the same global settings as a Hashid*Field. It is very important that the
options for the serializer field matches the model field, or else strange errors or data corruption can occur.HashidSerializerCharField will serialize the value into a Hashids string, but will deserialize either a Hashids string or
integer and save it into the underlying Hashid*Field properly. There is also a HashidSerializerIntegerField that will
serialize the Hashids into an un-encoded integer as well.Primary Key Related FieldsAny models that have a ForeignKey to another model that uses a Hashid*Field as its Primary Key will need to explicitly
define how thePrimaryKeyRelatedFieldshould serialize and deserialize the resulting value using thepk_fieldargument. If you don’t you will get an error
such as “Hashid(60): N8VNa8z is not JSON serializable”. We have to tell DRF how to serialize/deserialize Hashid*Fields.For the givenAuthormodel defined
above that has anid = HashidAutoField(primary_key=True)set, your BookSerializer should look like the following.fromrest_frameworkimportserializersfromhashid_field.restimportHashidSerializerCharFieldclassBookSerializer(serializers.ModelSerializer):author=serializers.PrimaryKeyRelatedField(pk_field=HashidSerializerCharField(source_field='library.Author.id'),read_only=True)classMeta:model=Bookfields=('id','author')Make sure you pass the source field to the HashidSerializer*Field so that it can copy the ‘salt’, ‘min_length’ and ‘alphabet’
as described above.This example setsread_only=Truebut you can explicitly define aquerysetor overrideget_queryset(self)to allow
read-write behavior.author=serializers.PrimaryKeyRelatedField(pk_field=HashidSerializerCharField(source_field='library.Author.id'),queryset=Author.objects.all())For a ManyToManyField, you must also remember to passmany=Trueto thePrimaryKeyRelatedField.HashidSerializerCharFieldSerialize a Hashid*Field to a Hashids string, de-serialize either a valid Hashids string or integer into a
Hashid*Field (if allow_int_lookup is enabled.)Parameterssource_fieldA 3-field dotted notation of the source field to load matching ‘salt’, ‘min_length’ and ‘alphabet’ settings from. Must
be in the format of “app_name.model_name.field_name”. Example: “library.Book.reference_id”.salt, min_length, alphabet, prefix, allow_int_lookupSeeField ParametersHashidSerializerIntegerFieldSerialize a Hashid*Field to an integer, de-serialize either a valid Hashids string or integer into a
Hashid*Field. SeeHashidSerializerCharFieldfor parameters.Please Note: This field will always serialize to an integer and thus will also de-serialize integers into valid
objects, regardless of theallow_int_lookupsetting.Known IssuesWith Django 5.0, attempting to filter on a field that is a ForeignKey to another model that uses a Hashid*Field as its
primary key will result in an error such as “‘Hashid’ object is not iterable”. The workaround is to specify the exact
field of the related model to filter on. e.g. instead oflist_filter = [‘author’]uselist_filter = [‘author__name’].DevelopmentHere are some rough instructions on how to set up a dev environment to develop this module. Modify as needed. The
sandbox is a django project that uses django-hashid-id, and is useful for developing features with.git clonehttps://github.com/nshafer/django-hashid-field.git&& cddjango-hashid-fieldmkvirtualenv-a.-p/usr/bin/python3-rsandbox/requirements.txtdjango-hashid-fieldpython setup.py developsandbox/manage.py migratesandbox/manage.py createsuperusersandbox/manage.py loaddata authors books editorssandbox/manage.py runserverpython runtests.pyFor any pull requests, clone the repo and push to it, then create the PR.To install the latest development version, use:` pip installgit+https://github.com/nshafer/django-hashid-field.git`LICENSEMIT License. You may use this in commercial and non-commercial projects with proper attribution.
Please see theLICENSE