Skip to content

Commit

Permalink
Add a 'prefix' feature (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacklinke authored Mar 11, 2024
1 parent 3b74f07 commit ade78da
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 101 deletions.
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ The project was forked from [django-hashids](https://github.com/ericls/django-ha

- Proxy the internal model `pk` field without storing the value in the database.
- Allows lookups and filtering by sqid string.
- Can be used as sort key
- Allows specifying a min_length and alphabet globally
- Supports custom min_length, and alphabet per field
- Supports Django REST Framework Serializers
- Can be used as sort key.
- Allows specifying a min_length and alphabet globally.
- Supports custom min_length, prefix, and alphabet per field.
- Supports Django REST Framework Serializers.
- Supports 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 other lookups: isnull, gt, gte, lt and lte.
- Supports filtering by `__iexact`, though matching is the same as `__exact`.
- Supports other lookups: `in`, `isnull`, `gt`, `gte`, `lt`, and `lte`.

# Install

Expand Down Expand Up @@ -63,7 +63,6 @@ TestModel.objects.filter(sqid__gt="1Z") # same as id__gt=1, would return instan
# Allows usage in queryset.values
TestModel.objects.values_list("sqid", flat=True) # ["1Z", "4x"]
TestModel.objects.filter(sqid__in=TestModel.objects.values("sqid"))

```

## Using with URLs
Expand Down Expand Up @@ -99,28 +98,33 @@ class MyModelAdmin(admin.ModelAdmin):

## Config

The folloing attributes can be added in settings file to set default arguments of `SqidsField`:
The following attributes can be added in settings file to set default arguments of `SqidsField`:

1. `DJANGO_SQIDS_MIN_LENGTH`: default minimum length
2. `DJANGO_SQIDS_ALPHABET`: default alphabet

`SqidsField` does not reqiure any arguments but the following arguments can be supplied to modify its behavior.

| Name | Description |
| ----------------- | :-----------------------------------------------------: |
| `real_field_name` | The proxied field name |
| `sqids_instance` | The sqids instance used to encode/decode for this field |
| `min_length` | The minimum length of sqids generated for this field |
| `alphabet` | The alphabet used by this field to generate sqids |
| Name | Description | Example |
| ----------------- | :-----------------------------------------------------: | ----------------------------------------------------------- |
| `real_field_name` | The proxied field name | sqid = SqidsField(real_field_name="id") |
| `sqids_instance` | The sqids instance used to encode/decode for this field | sqid = SqidsField(sqids_instance=sqids_instance) |
| `min_length` | The minimum length of sqids generated for this field | sqid = SqidsField(min_length=10) |
| `alphabet` | The alphabet used by this field to generate sqids | sqid = SqidsField(alphabet="KHE5J3L2M4N6P7Q8R9T0V1W2X3Y4Z") |
| `prefix` | The prefix used by this field to generate sqids | sqid = SqidsField(prefix="item-") |

The argument `sqids_instance` is mutually exclusive to `min_length` and `alphabet`. See [sqids-python](https://github.com/sqids/sqids-python) for more info about the arguments.

Some common Model arguments such as `verbose_name` are also supported.

## Where did the Salt go?

[Sqids removed the "salt" parameter](https://sqids.org/faq#salt) to prevent association with security or safety.
`django_sqids` provides a useful `shuffle_alphabet` function that helps reintroduce the same idea:
When the Hashids project transitioned to Sqids, [Sqids removed the "salt" parameter](https://sqids.org/faq#salt) to prevent the appearance that
it provides security or safety. In Sqids, the order of the alphabet affects the generated sqids. `django_sqids` provides a useful `shuffle_alphabet`
function that helps reintroduce the same idea as the "salt" parameter by shuffling the alphabet. This can be used to generate a unique alphabet for each
instance of `SqidsField` to prevent the same id from generating the same sqid across different instances of `SqidsField`.

The `seed` parameter is used to generate a unique ordering of alphabet for each instance of `SqidsField`. The `alphabet` parameter can be used to specify a custom alphabet.

```python
from django_sqids import SqidsField, shuffle_alphabet
Expand Down
17 changes: 14 additions & 3 deletions django_sqids/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ def __init__(
sqids_instance=None,
alphabet=None,
min_length=None,
**kwargs
prefix="",
**kwargs,
):
kwargs.pop("editable", None)
super().__init__(*args, editable=False, **kwargs)
self.real_field_name = real_field_name
self.min_length = min_length
self.alphabet = alphabet
self.prefix = prefix
self._explicit_sqids_instance = sqids_instance

self.sqids_instance = None
Expand Down Expand Up @@ -93,13 +95,20 @@ def get_sqid_instance(self):
return Sqids(min_length=min_length, alphabet=alphabet)

def get_prep_value(self, value):
if self.prefix:
if value.startswith(self.prefix):
value = value[len(self.prefix) :]
else:
return None
decoded_values = self.sqids_instance.decode(value)
if not decoded_values:
return None
return decoded_values[0]

def from_db_value(self, value, expression, connection, *args):
return self.sqids_instance.encode([value])
# Prepend the prefix when encoding for display
encoded_value = self.sqids_instance.encode([value])
return f"{self.prefix}{encoded_value}" if encoded_value is not None else None

def get_col(self, alias, output_field=None):
if output_field is None:
Expand Down Expand Up @@ -135,7 +144,9 @@ def __get__(self, instance, name=None):
if real_value is None:
return ""
assert isinstance(real_value, int)
return self.sqids_instance.encode([real_value])
# Prepend the prefix when encoding for display
encoded_value = self.sqids_instance.encode([real_value])
return f"{self.prefix}{encoded_value}"

def __set__(self, instance, value):
pass
Expand Down
Loading

0 comments on commit ade78da

Please sign in to comment.