django-display-ids 0.1.4__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,10 @@ Example:
23
23
  display_id_prefix = "inv"
24
24
  """
25
25
 
26
+ from typing import Any
27
+
26
28
  from .admin import DisplayIDSearchMixin
29
+ from .converters import DisplayIDConverter, DisplayIDOrUUIDConverter, UUIDConverter
27
30
  from .encoding import (
28
31
  decode_display_id,
29
32
  decode_uuid,
@@ -45,12 +48,29 @@ from .exceptions import (
45
48
  UnknownPrefixError,
46
49
  )
47
50
  from .managers import DisplayIDManager, DisplayIDQuerySet
48
- from .models import DisplayIDMixin, get_model_for_prefix
49
51
  from .resolver import resolve_object
50
52
  from .typing import DEFAULT_STRATEGIES, StrategyName
51
53
  from .views import DisplayIDObjectMixin
52
54
 
55
+
56
+ def __getattr__(name: str) -> Any:
57
+ """Lazy import for model-related items to avoid app registry issues."""
58
+ if name == "DisplayIDMixin":
59
+ from .models import DisplayIDMixin
60
+
61
+ return DisplayIDMixin
62
+ if name == "get_model_for_prefix":
63
+ from .models import get_model_for_prefix
64
+
65
+ return get_model_for_prefix
66
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
67
+
68
+
53
69
  __all__ = [ # noqa: RUF022 - keep categorized order for readability
70
+ # URL converters
71
+ "DisplayIDConverter",
72
+ "UUIDConverter",
73
+ "DisplayIDOrUUIDConverter",
54
74
  # Encoding
55
75
  "encode_uuid",
56
76
  "decode_uuid",
@@ -85,10 +105,8 @@ __all__ = [ # noqa: RUF022 - keep categorized order for readability
85
105
  "DEFAULT_STRATEGIES",
86
106
  ]
87
107
 
88
- __version__ = "0.1.1"
89
-
90
108
 
91
- def get_drf_mixin():
109
+ def get_drf_mixin() -> type:
92
110
  """Lazily import the DRF mixin to avoid hard dependency.
93
111
 
94
112
  Returns:
@@ -4,12 +4,12 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import uuid
7
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from .encoding import decode_display_id
10
10
 
11
11
  if TYPE_CHECKING:
12
- from django.db.models import QuerySet
12
+ from django.db.models import Model, QuerySet
13
13
  from django.http import HttpRequest
14
14
 
15
15
  __all__ = ["DisplayIDSearchMixin"]
@@ -39,13 +39,15 @@ class DisplayIDSearchMixin:
39
39
  """
40
40
 
41
41
  uuid_field: str | None = None
42
+ model: type[Model]
42
43
 
43
44
  def _get_uuid_field(self) -> str:
44
45
  """Get the UUID field name to search."""
45
46
  if self.uuid_field is not None:
46
47
  return self.uuid_field
47
48
  # Try to get from model's uuid_field attribute
48
- return getattr(self.model, "uuid_field", None) or "id"
49
+ uuid_field: str | None = getattr(self.model, "uuid_field", None)
50
+ return uuid_field or "id"
49
51
 
50
52
  def _try_parse_uuid(self, value: str) -> uuid.UUID | None:
51
53
  """Try to parse a string as a UUID."""
@@ -57,16 +59,16 @@ class DisplayIDSearchMixin:
57
59
  def get_search_results(
58
60
  self,
59
61
  request: HttpRequest,
60
- queryset: QuerySet,
62
+ queryset: QuerySet[Any],
61
63
  search_term: str,
62
- ) -> tuple[QuerySet, bool]:
64
+ ) -> tuple[QuerySet[Any], bool]:
63
65
  """Extend search to handle display IDs and raw UUIDs.
64
66
 
65
67
  Tries to match the search term as:
66
68
  1. A display ID (prefix_base62uuid) if it contains an underscore
67
69
  2. A raw UUID if it looks like a UUID format
68
70
  """
69
- queryset, use_distinct = super().get_search_results(
71
+ queryset, use_distinct = super().get_search_results( # type: ignore[misc]
70
72
  request, queryset, search_term
71
73
  )
72
74
 
@@ -0,0 +1,11 @@
1
+ """Django app configuration for django-display-ids."""
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class DjangoDisplayIdsConfig(AppConfig):
7
+ """App configuration for django-display-ids."""
8
+
9
+ name = "django_display_ids"
10
+ verbose_name = "Django Display IDs"
11
+ default_auto_field = "django.db.models.BigAutoField"
@@ -45,5 +45,8 @@ def get_setting(name: str) -> str | tuple[StrategyName, ...]:
45
45
  if name not in DEFAULTS:
46
46
  raise KeyError(f"Unknown setting: {name}")
47
47
 
48
- user_settings = getattr(settings, "DISPLAY_IDS", {})
49
- return user_settings.get(name, DEFAULTS[name])
48
+ user_settings: dict[str, str | tuple[str, ...]] = getattr(
49
+ settings, "DISPLAY_IDS", {}
50
+ )
51
+ result = user_settings.get(name, DEFAULTS[name])
52
+ return result # type: ignore[return-value]
@@ -6,16 +6,21 @@ proper OpenAPI schema generation for DisplayIDField.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from typing import TYPE_CHECKING, Any
10
+
9
11
  try:
10
12
  from drf_spectacular.extensions import OpenApiSerializerFieldExtension
11
13
  except ImportError:
12
14
  # drf-spectacular not installed, skip extension registration
13
15
  pass
14
16
  else:
17
+ if TYPE_CHECKING:
18
+ from drf_spectacular.openapi import AutoSchema
19
+
15
20
  from django_display_ids.encoding import ENCODED_UUID_LENGTH, encode_uuid
16
21
  from django_display_ids.examples import example_uuid_for_prefix
17
22
 
18
- class DisplayIDFieldExtension(OpenApiSerializerFieldExtension):
23
+ class DisplayIDFieldExtension(OpenApiSerializerFieldExtension): # type: ignore[no-untyped-call]
19
24
  """OpenAPI schema extension for DisplayIDField.
20
25
 
21
26
  Generates schema with correct prefix example based on the field's
@@ -27,7 +32,7 @@ else:
27
32
  )
28
33
  match_subclasses = True
29
34
 
30
- def _get_model_from_view(self, auto_schema):
35
+ def _get_model_from_view(self, auto_schema: AutoSchema | None) -> Any:
31
36
  """Try to get model from the view's queryset."""
32
37
  if auto_schema is None:
33
38
  return None
@@ -48,7 +53,9 @@ else:
48
53
  return queryset.model
49
54
  return None
50
55
 
51
- def map_serializer_field(self, auto_schema, direction):
56
+ def map_serializer_field(
57
+ self, auto_schema: AutoSchema, direction: str
58
+ ) -> dict[str, Any]:
52
59
  """Generate OpenAPI schema for DisplayIDField."""
53
60
  # Get prefix from field override or try to get from model
54
61
  prefix = self.target._prefix_override
@@ -95,9 +95,9 @@ class DisplayIDField(serializers.SerializerMethodField):
95
95
  # If using prefix override, generate display_id with that prefix
96
96
  if self._prefix_override is not None:
97
97
  # Get uuid_field name from model, then fall back to settings
98
- uuid_field_name = getattr(obj, "uuid_field", None)
98
+ uuid_field_name: str | None = getattr(obj, "uuid_field", None)
99
99
  if uuid_field_name is None:
100
- uuid_field_name = get_setting("UUID_FIELD")
100
+ uuid_field_name = str(get_setting("UUID_FIELD"))
101
101
  uuid_value = getattr(obj, uuid_field_name, None)
102
102
  if uuid_value is None:
103
103
  raise ValueError(
@@ -108,7 +108,8 @@ class DisplayIDField(serializers.SerializerMethodField):
108
108
 
109
109
  # Use the model's display_id property
110
110
  if hasattr(obj, "display_id"):
111
- return obj.display_id
111
+ display_id: str = obj.display_id
112
+ return display_id
112
113
 
113
114
  raise ValueError(
114
115
  f"Cannot generate display_id: {obj.__class__.__name__} "
@@ -174,4 +174,4 @@ class DisplayIDLookupMixin:
174
174
  # Check object-level permissions
175
175
  self.check_object_permissions(self.request, obj)
176
176
 
177
- return obj
177
+ return obj # type: ignore[no-any-return]
@@ -0,0 +1,111 @@
1
+ """Django URL path converters for display IDs and UUIDs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "DisplayIDConverter",
7
+ "DisplayIDOrUUIDConverter",
8
+ "UUIDConverter",
9
+ ]
10
+
11
+
12
+ class DisplayIDConverter:
13
+ """Path converter for display IDs.
14
+
15
+ Matches the format: {prefix}_{base62} where prefix is 1-16 lowercase
16
+ letters and base62 is exactly 22 alphanumeric characters.
17
+
18
+ Example:
19
+ from django.urls import path, register_converter
20
+ from django_display_ids.converters import DisplayIDConverter
21
+
22
+ register_converter(DisplayIDConverter, "display_id")
23
+
24
+ urlpatterns = [
25
+ path("invoices/<display_id:id>/", InvoiceDetailView.as_view()),
26
+ ]
27
+ """
28
+
29
+ regex = r"[a-z]{1,16}_[0-9A-Za-z]{22}"
30
+
31
+ def to_python(self, value: str) -> str:
32
+ """Convert the URL value to a Python object."""
33
+ return value
34
+
35
+ def to_url(self, value: str) -> str:
36
+ """Convert a Python object to a URL string."""
37
+ return value
38
+
39
+
40
+ class UUIDConverter:
41
+ """Path converter for UUIDs.
42
+
43
+ Matches UUIDs in both hyphenated and unhyphenated formats:
44
+ - 550e8400-e29b-41d4-a716-446655440000 (hyphenated)
45
+ - 550e8400e29b41d4a716446655440000 (unhyphenated)
46
+
47
+ Example:
48
+ from django.urls import path, register_converter
49
+ from django_display_ids.converters import UUIDConverter
50
+
51
+ register_converter(UUIDConverter, "uuid")
52
+
53
+ urlpatterns = [
54
+ path("invoices/<uuid:id>/", InvoiceDetailView.as_view()),
55
+ ]
56
+
57
+ Note:
58
+ Django's built-in UUIDConverter only accepts hyphenated UUIDs.
59
+ This converter is more permissive.
60
+ """
61
+
62
+ # Hyphenated: 8-4-4-4-12 hex chars with hyphens
63
+ # Unhyphenated: 32 hex chars
64
+ # Note: Parentheses group the alternatives so ^ and $ anchor correctly
65
+ regex = (
66
+ r"(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32})"
67
+ )
68
+
69
+ def to_python(self, value: str) -> str:
70
+ """Convert the URL value to a Python object."""
71
+ return value
72
+
73
+ def to_url(self, value: str) -> str:
74
+ """Convert a Python object to a URL string."""
75
+ return value
76
+
77
+
78
+ class DisplayIDOrUUIDConverter:
79
+ """Path converter for display IDs or UUIDs.
80
+
81
+ Matches either format:
82
+ - Display ID: {prefix}_{base62}
83
+ - UUID: hyphenated or unhyphenated
84
+
85
+ Example:
86
+ from django.urls import path, register_converter
87
+ from django_display_ids.converters import DisplayIDOrUUIDConverter
88
+
89
+ register_converter(DisplayIDOrUUIDConverter, "display_id_or_uuid")
90
+
91
+ urlpatterns = [
92
+ path("invoices/<display_id_or_uuid:id>/", InvoiceDetailView.as_view()),
93
+ ]
94
+ """
95
+
96
+ # Note: Parentheses group the alternatives so ^ and $ anchor correctly
97
+ regex = (
98
+ r"(?:"
99
+ r"[a-z]{1,16}_[0-9A-Za-z]{22}"
100
+ r"|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
101
+ r"|[0-9a-f]{32}"
102
+ r")"
103
+ )
104
+
105
+ def to_python(self, value: str) -> str:
106
+ """Convert the URL value to a Python object."""
107
+ return value
108
+
109
+ def to_url(self, value: str) -> str:
110
+ """Convert a Python object to a URL string."""
111
+ return value
@@ -26,7 +26,7 @@ def _get_prefix(prefix_or_model: str | type[Model]) -> str:
26
26
  if isinstance(prefix_or_model, str):
27
27
  return prefix_or_model
28
28
  # It's a model class
29
- prefix = getattr(prefix_or_model, "display_id_prefix", None)
29
+ prefix: str | None = getattr(prefix_or_model, "display_id_prefix", None)
30
30
  if prefix is None:
31
31
  raise ValueError(f"Model {prefix_or_model.__name__} has no display_id_prefix")
32
32
  return prefix
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import TYPE_CHECKING, Any, TypeVar
6
6
 
7
7
  from django.db import models
8
+ from django.db.models import Q
8
9
 
9
10
  from .conf import get_setting
10
11
  from .encoding import decode_display_id
@@ -18,6 +19,8 @@ from .exceptions import (
18
19
  from .strategies import parse_identifier
19
20
 
20
21
  if TYPE_CHECKING:
22
+ from collections.abc import Sequence
23
+
21
24
  from .typing import StrategyName
22
25
 
23
26
  __all__ = [
@@ -91,7 +94,7 @@ class DisplayIDQuerySet(models.QuerySet[M]):
91
94
  # Query the database
92
95
  try:
93
96
  return self.get(**{uuid_field: uuid_value})
94
- except model.DoesNotExist:
97
+ except model.DoesNotExist: # type: ignore[attr-defined]
95
98
  raise ObjectNotFoundError(value, model_name=model.__name__) from None
96
99
 
97
100
  def get_by_identifier(
@@ -140,22 +143,85 @@ class DisplayIDQuerySet(models.QuerySet[M]):
140
143
  # Execute the query
141
144
  try:
142
145
  return self.get(**lookup)
143
- except model.DoesNotExist:
146
+ except model.DoesNotExist: # type: ignore[attr-defined]
144
147
  raise ObjectNotFoundError(value, model_name=model.__name__) from None
145
- except model.MultipleObjectsReturned:
148
+ except model.MultipleObjectsReturned: # type: ignore[attr-defined]
146
149
  count = self.filter(**lookup).count()
147
150
  raise AmbiguousIdentifierError(value, count) from None
148
151
 
152
+ def get_by_identifiers(
153
+ self,
154
+ values: Sequence[str],
155
+ *,
156
+ strategies: tuple[StrategyName, ...] | None = None,
157
+ prefix: str | None = None,
158
+ ) -> DisplayIDQuerySet[M]:
159
+ """Get multiple objects by any supported identifier type in a single query.
160
+
161
+ Parses each identifier to determine its type (display ID, UUID, or slug),
162
+ then executes a single database query using `__in` lookups.
163
+
164
+ Args:
165
+ values: A sequence of identifier strings (display IDs, UUIDs, or slugs).
166
+ strategies: Strategies to try. Defaults to settings.
167
+ prefix: Expected display ID prefix for validation.
168
+
169
+ Returns:
170
+ A queryset containing matching objects. Order is not guaranteed
171
+ to match input order. Missing identifiers are silently excluded.
172
+
173
+ Raises:
174
+ InvalidIdentifierError: If any identifier cannot be parsed.
175
+
176
+ Example:
177
+ invoices = Invoice.objects.get_by_identifiers([
178
+ 'inv_2aUyqjCzEIiEcYMKj7TZtw',
179
+ 'inv_7kN3xPqRmLwYvTzJ5HfUaB',
180
+ '550e8400-e29b-41d4-a716-446655440000',
181
+ ])
182
+ """
183
+ if not values:
184
+ return self.none()
185
+
186
+ uuid_field = self._get_uuid_field()
187
+ slug_field = self._get_slug_field()
188
+ expected_prefix = prefix or self._get_model_prefix()
189
+ lookup_strategies = strategies or self._get_strategies()
190
+
191
+ # Collect UUIDs and slugs separately
192
+ uuids: list[Any] = []
193
+ slugs: list[str] = []
194
+
195
+ for value in values:
196
+ result = parse_identifier(
197
+ value, lookup_strategies, expected_prefix=expected_prefix
198
+ )
199
+ if result.strategy in ("uuid", "display_id"):
200
+ uuids.append(result.uuid)
201
+ else:
202
+ slugs.append(result.slug) # type: ignore[arg-type]
203
+
204
+ # Build query with OR conditions
205
+ query = Q()
206
+ if uuids:
207
+ query |= Q(**{f"{uuid_field}__in": uuids})
208
+ if slugs:
209
+ query |= Q(**{f"{slug_field}__in": slugs})
210
+
211
+ return self.filter(query)
212
+
149
213
  def _get_uuid_field(self) -> str:
150
214
  """Get the UUID field name for this model."""
151
215
  if hasattr(self.model, "_get_uuid_field"):
152
- return self.model._get_uuid_field() # type: ignore[attr-defined]
216
+ result: str = self.model._get_uuid_field() # type: ignore[attr-defined]
217
+ return result
153
218
  return str(get_setting("UUID_FIELD"))
154
219
 
155
220
  def _get_slug_field(self) -> str:
156
221
  """Get the slug field name for this model."""
157
222
  if hasattr(self.model, "_get_slug_field"):
158
- return self.model._get_slug_field() # type: ignore[attr-defined]
223
+ result: str = self.model._get_slug_field() # type: ignore[attr-defined]
224
+ return result
159
225
  return str(get_setting("SLUG_FIELD"))
160
226
 
161
227
  def _get_strategies(self) -> tuple[StrategyName, ...]:
@@ -166,7 +232,8 @@ class DisplayIDQuerySet(models.QuerySet[M]):
166
232
  """Get the display ID prefix from the model, if defined."""
167
233
  if hasattr(self.model, "get_display_id_prefix"):
168
234
  try:
169
- return self.model.get_display_id_prefix() # type: ignore[attr-defined]
235
+ result: str | None = self.model.get_display_id_prefix() # type: ignore[attr-defined]
236
+ return result
170
237
  except NotImplementedError:
171
238
  return None
172
239
  return None
@@ -210,3 +277,18 @@ class DisplayIDManager(models.Manager[M]):
210
277
  return self.get_queryset().get_by_identifier(
211
278
  value, strategies=strategies, prefix=prefix
212
279
  )
280
+
281
+ def get_by_identifiers(
282
+ self,
283
+ values: Sequence[str],
284
+ *,
285
+ strategies: tuple[StrategyName, ...] | None = None,
286
+ prefix: str | None = None,
287
+ ) -> DisplayIDQuerySet[M]:
288
+ """Get multiple objects by any supported identifier type.
289
+
290
+ See DisplayIDQuerySet.get_by_identifiers for details.
291
+ """
292
+ return self.get_queryset().get_by_identifiers(
293
+ values, strategies=strategies, prefix=prefix
294
+ )
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import ClassVar
5
+ from typing import Any, ClassVar
6
6
 
7
7
  from django.db import models
8
8
 
@@ -84,7 +84,7 @@ class DisplayIDMixin(models.Model):
84
84
  class Meta:
85
85
  abstract = True
86
86
 
87
- def __init_subclass__(cls, **kwargs) -> None:
87
+ def __init_subclass__(cls, **kwargs: Any) -> None:
88
88
  """Register prefix when subclass is created."""
89
89
  super().__init_subclass__(**kwargs)
90
90
 
@@ -134,4 +134,4 @@ class DisplayIDMixin(models.Model):
134
134
  return encode_display_id(prefix, uuid_value)
135
135
 
136
136
  # Django admin display configuration
137
- display_id.fget.short_description = "Display ID"
137
+ display_id.fget.short_description = "Display ID" # type: ignore[attr-defined]
@@ -70,8 +70,8 @@ def resolve_object(
70
70
  # Execute the query
71
71
  try:
72
72
  return qs.get(**lookup)
73
- except model.DoesNotExist:
73
+ except model.DoesNotExist: # type: ignore[attr-defined]
74
74
  raise ObjectNotFoundError(value, model_name=model.__name__) from None
75
- except model.MultipleObjectsReturned:
75
+ except model.MultipleObjectsReturned: # type: ignore[attr-defined]
76
76
  count = qs.filter(**lookup).count()
77
77
  raise AmbiguousIdentifierError(value, count) from None
File without changes
@@ -0,0 +1,48 @@
1
+ """Template tags and filters for generating display IDs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+
7
+ from django import template
8
+
9
+ from ..encoding import encode_display_id
10
+
11
+ __all__ = [
12
+ "display_id",
13
+ ]
14
+
15
+ register = template.Library()
16
+
17
+
18
+ @register.filter(name="display_id")
19
+ def display_id(value: uuid.UUID | None, prefix: str) -> str:
20
+ """Encode a UUID as a display ID.
21
+
22
+ Usage:
23
+ {{ some_uuid|display_id:"inv" }}
24
+ {{ invoice.customer_id|display_id:"cust" }}
25
+
26
+ Args:
27
+ value: UUID to encode.
28
+ prefix: Display ID prefix (1-16 lowercase letters).
29
+
30
+ Returns:
31
+ Display ID string like "inv_2aUyqjCzEIiEcYMKj7TZtw", or empty string
32
+ if value is None.
33
+
34
+ Raises:
35
+ TemplateSyntaxError: If prefix is invalid or value is not a UUID.
36
+ """
37
+ if value is None:
38
+ return ""
39
+
40
+ if not isinstance(value, uuid.UUID):
41
+ raise template.TemplateSyntaxError(
42
+ f"display_id filter requires a UUID, got {type(value).__name__}"
43
+ )
44
+
45
+ try:
46
+ return encode_display_id(prefix, value)
47
+ except ValueError as e:
48
+ raise template.TemplateSyntaxError(str(e)) from e
@@ -133,7 +133,7 @@ class DisplayIDObjectMixin:
133
133
  qs = queryset if queryset is not None else self.get_queryset()
134
134
 
135
135
  try:
136
- return resolve_object(
136
+ return resolve_object( # type: ignore[no-any-return]
137
137
  model=self.model,
138
138
  value=str(value),
139
139
  strategies=self._get_strategies(),
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.3
2
+ Name: django-display-ids
3
+ Version: 0.3.0
4
+ Summary: Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
5
+ Keywords: django,stripe,uuid,base62,prefixed-id,drf,shortuuid,nanoid,ulid
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Framework :: Django
9
+ Classifier: Framework :: Django :: 4.2
10
+ Classifier: Framework :: Django :: 5.2
11
+ Classifier: Framework :: Django :: 6.0
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: django>=4.2
19
+ Requires-Python: >=3.12
20
+ Project-URL: Documentation, https://django-display-ids.readthedocs.io/
21
+ Project-URL: Repository, https://github.com/josephabrahams/django-display-ids
22
+ Description-Content-Type: text/markdown
23
+
24
+ # django-display-ids
25
+
26
+ [![PyPI](https://img.shields.io/pypi/v/django-display-ids)](https://pypi.org/project/django-display-ids/)
27
+ [![Python](https://img.shields.io/pypi/pyversions/django-display-ids)](https://pypi.org/project/django-display-ids/)
28
+ [![Django](https://img.shields.io/badge/django-4.2%20%7C%205.2%20%7C%206.0-blue)](https://pypi.org/project/django-display-ids/)
29
+ [![CI](https://github.com/josephabrahams/django-display-ids/actions/workflows/ci.yml/badge.svg)](https://github.com/josephabrahams/django-display-ids/actions/workflows/ci.yml)
30
+ [![codecov](https://codecov.io/gh/josephabrahams/django-display-ids/graph/badge.svg)](https://codecov.io/gh/josephabrahams/django-display-ids)
31
+ [![Docs](https://readthedocs.org/projects/django-display-ids/badge/?version=stable)](https://django-display-ids.readthedocs.io/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/josephabrahams/django-display-ids/blob/main/LICENSE)
33
+
34
+ Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
35
+
36
+ ## Why?
37
+
38
+ UUIDv7 (native in Python 3.14+) offers excellent database performance with time-ordered indexing. But they lack context — seeing `550e8400-e29b-41d4-a716-446655440000` in a URL or log doesn't tell you what kind of object it refers to.
39
+
40
+ Display IDs like `inv_2aUyqjCzEIiEcYMKj7TZtw` are more useful: the prefix identifies the object type at a glance, and they're compact and URL-safe. But storing display IDs in the database is far less efficient than native UUIDs.
41
+
42
+ Different consumers have different needs:
43
+ - **Humans** prefer slugs (`my-invoice`) or display IDs (`inv_xxx`)
44
+ - **APIs and integrations** work well with UUIDs
45
+
46
+ This library gives you the best of both worlds: accept any format in your URLs and API endpoints, then translate to an efficient UUID lookup in the database. Store UUIDs, expose whatever format your users need.
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pip install django-display-ids
52
+ ```
53
+
54
+ Add to `INSTALLED_APPS`:
55
+
56
+ ```python
57
+ INSTALLED_APPS = [
58
+ # ...
59
+ "django_display_ids",
60
+ ]
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ **Django views:**
66
+
67
+ ```python
68
+ from django.views.generic import DetailView
69
+ from django_display_ids import DisplayIDObjectMixin
70
+
71
+ class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
72
+ model = Invoice
73
+ lookup_param = "id"
74
+ lookup_strategies = ("display_id", "uuid", "slug")
75
+ display_id_prefix = "inv"
76
+ ```
77
+
78
+ **Django REST Framework:**
79
+
80
+ ```python
81
+ from rest_framework.viewsets import ModelViewSet
82
+ from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin
83
+
84
+ class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
85
+ queryset = Invoice.objects.all()
86
+ serializer_class = InvoiceSerializer
87
+ lookup_url_kwarg = "id"
88
+ lookup_strategies = ("display_id", "uuid", "slug")
89
+ display_id_prefix = "inv"
90
+ ```
91
+
92
+ Now your views accept:
93
+ - `inv_2aUyqjCzEIiEcYMKj7TZtw` (display ID)
94
+ - `550e8400-e29b-41d4-a716-446655440000` (UUID)
95
+ - `my-invoice-slug` (slug)
96
+
97
+ **Templates:**
98
+
99
+ ```django
100
+ {% load display_ids %}
101
+
102
+ {{ invoice.display_id }} {# inv_2aUyqjCzEIiEcYMKj7TZtw #}
103
+ {{ order.customer_id|display_id:"cust" }} {# encode any UUID #}
104
+ ```
105
+
106
+ ## Features
107
+
108
+ - **Multiple identifier formats**: display ID (`prefix_base62uuid`), UUID (v4/v7), slug
109
+ - **Framework support**: Django CBVs and Django REST Framework
110
+ - **Template filter**: Encode UUIDs as display IDs in templates
111
+ - **Zero model changes required**: Works with any existing UUID field
112
+ - **OpenAPI integration**: Automatic schema generation with drf-spectacular
113
+
114
+ ## Documentation
115
+
116
+ Full documentation at [django-display-ids.readthedocs.io](https://django-display-ids.readthedocs.io/).
117
+
118
+ ## Contributing
119
+
120
+ See the [contributing guide](https://django-display-ids.readthedocs.io/en/latest/contributing.html).
121
+
122
+ ## Related Projects
123
+
124
+ If you need ID generation and storage (custom model fields), consider:
125
+
126
+ - **[django-prefix-id](https://github.com/jaddison/django-prefix-id)** — PrefixIDField that generates and stores base62-encoded UUIDs
127
+ - **[django-spicy-id](https://github.com/mik3y/django-spicy-id)** — Drop-in AutoField replacement
128
+ - **[django-charid-field](https://github.com/yunojuno/django-charid-field)** — CharField wrapper supporting cuid, ksuid, ulid
129
+
130
+ **django-display-ids** works with existing UUID fields and handles resolution only — no migrations required.
@@ -0,0 +1,25 @@
1
+ django_display_ids/__init__.py,sha256=2qX0YC7Jwpoom5rLOhvOewR3GQEIbUhu5ZYwjfEoklo,3207
2
+ django_display_ids/admin.py,sha256=_voqWbr8AwPRC_uCTJWTcEhAhc7RZUgvs7DyVsutDuw,3046
3
+ django_display_ids/apps.py,sha256=UqblGiYNONOIEH-giEAuKp1YDgxl2yf0jS0ELMj1iig,315
4
+ django_display_ids/conf.py,sha256=Mg5ZIhTRSF5dzNO_iYndoF4_g5Umo-5oUw1vB76M7fw,1230
5
+ django_display_ids/contrib/__init__.py,sha256=sxGJK8Whb6cL7RqACqGRYrIZvaMwP3l6dYk3mIYWzDY,62
6
+ django_display_ids/contrib/drf_spectacular/__init__.py,sha256=21n56CH7tp2lKq4EGW99KVEgVB9fJ5GnfllUW_KrIe8,4003
7
+ django_display_ids/contrib/rest_framework/__init__.py,sha256=Xun6zMhCZzQJZQ7ywvBYoKhTJIZEV84AntrbSWfBjYI,1567
8
+ django_display_ids/contrib/rest_framework/serializers.py,sha256=Jp-z7qHafxkGNYv30YC9rrqLt936SrhONJv3rqfQaC0,4049
9
+ django_display_ids/contrib/rest_framework/views.py,sha256=nDaze7MJwVqL0MrxQLOur3sPVrRXud_WT4ijGR02jDY,6087
10
+ django_display_ids/converters.py,sha256=9xP3vKW1keN1glJvwmZun0BaBRLMKio0aD7Coc888ms,3197
11
+ django_display_ids/encoding.py,sha256=csIwUZaQKSOLwRU6-DWGTNGvSxmroyK0Yt7TBCo0AFE,2945
12
+ django_display_ids/examples.py,sha256=gap5NNPTmE7B5uxiYKoMoK8G-OEtL1Ek0W039l6oJ9I,2689
13
+ django_display_ids/exceptions.py,sha256=nmyRfpsqVvz226Zcu_QANwr8MudbfoX09mAgOCwuPuQ,3022
14
+ django_display_ids/managers.py,sha256=PymcK4BZL6UsUOtoloHP34MCRNmvNHSKEcOImhZxGag,9779
15
+ django_display_ids/models.py,sha256=r2SGrP-6g2LeiZZ4yC1Zp8CcJQ5VvpXS8kQMXl2FBgU,4196
16
+ django_display_ids/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ django_display_ids/resolver.py,sha256=TJub6nT6JFThanxETbH8kXVliScjFiksD2kGpu0OvXA,2554
18
+ django_display_ids/strategies.py,sha256=Rq00-AW_FB8-K04u2oBK5J6kPiYgsE3TdYlLyK_zro0,4436
19
+ django_display_ids/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ django_display_ids/templatetags/display_ids.py,sha256=4KHE8r8mgSKb7LgIuXJaJB_3UGrzRZvTdLqSCYQtb5I,1157
21
+ django_display_ids/typing.py,sha256=2O3kT7XKkiE7WI9A5KkILPM-Zi7-zCy5gVvXQL_J2mI,478
22
+ django_display_ids/views.py,sha256=-y_Zwo4QLU0lPRPjABpijsze5vsG0CBvJtrVwVtuLwM,5127
23
+ django_display_ids-0.3.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
24
+ django_display_ids-0.3.0.dist-info/METADATA,sha256=9QObcv7ll6Nt2c-WzRF-KCm3olO0RNheX-xKUfqzLEc,5299
25
+ django_display_ids-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.28
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,422 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: django-display-ids
3
- Version: 0.1.4
4
- Summary: Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
5
- Keywords: django,stripe,uuid,base62,prefixed-id,drf,shortuuid,nanoid,ulid
6
- License: ISC
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Framework :: Django
9
- Classifier: Framework :: Django :: 4.2
10
- Classifier: Framework :: Django :: 5.2
11
- Classifier: Framework :: Django :: 6.0
12
- Classifier: License :: OSI Approved :: ISC License (ISCL)
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
- Classifier: Typing :: Typed
18
- Requires-Dist: django>=4.2
19
- Requires-Python: >=3.12
20
- Project-URL: Homepage, https://joseph.is/django-display-ids
21
- Description-Content-Type: text/markdown
22
-
23
- # django-display-ids
24
-
25
- Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
26
-
27
- Display IDs are human-friendly identifiers like `inv_2aUyqjCzEIiEcYMKj7TZtw` — a short prefix indicating the object type, followed by a base62-encoded UUID. This format, popularized by Stripe, makes IDs recognizable at a glance while remaining URL-safe and compact.
28
-
29
- This library focuses on **lookup only** — it works with your existing UUID fields and requires no migrations or schema changes.
30
-
31
- ## Installation
32
-
33
- ```bash
34
- pip install django-display-ids
35
- ```
36
-
37
- No `INSTALLED_APPS` entry required — just import and use.
38
-
39
- ## Quick Start
40
-
41
- ```python
42
- from django.views.generic import DetailView
43
- from django_display_ids import DisplayIDObjectMixin
44
-
45
- class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
46
- model = Invoice
47
- lookup_param = "id"
48
- lookup_strategies = ("display_id", "uuid")
49
- display_id_prefix = "inv"
50
- ```
51
-
52
- ```python
53
- # urls.py
54
- urlpatterns = [
55
- path("invoices/<str:id>/", InvoiceDetailView.as_view()),
56
- ]
57
- ```
58
-
59
- Now your view accepts both formats:
60
- - `inv_2aUyqjCzEIiEcYMKj7TZtw` (display ID)
61
- - `550e8400-e29b-41d4-a716-446655440000` (UUID)
62
-
63
- ## Features
64
-
65
- - **Multiple identifier formats**: display ID (`prefix_base62uuid`), UUID (v4/v7), slug
66
- - **Framework support**: Django CBVs and Django REST Framework
67
- - **Zero model changes required**: Works with any existing UUID field
68
- - **Stateless**: Pure lookup, no database writes
69
-
70
- ## Usage
71
-
72
- ### Django Class-Based Views
73
-
74
- ```python
75
- from django.views.generic import DetailView, UpdateView, DeleteView
76
- from django_display_ids import DisplayIDObjectMixin
77
-
78
- class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
79
- model = Invoice
80
- lookup_param = "id"
81
- lookup_strategies = ("display_id", "uuid")
82
- display_id_prefix = "inv"
83
-
84
- # Works with any view that uses get_object()
85
- class InvoiceUpdateView(DisplayIDObjectMixin, UpdateView):
86
- model = Invoice
87
- lookup_param = "id"
88
- display_id_prefix = "inv"
89
- ```
90
-
91
- ### Django REST Framework
92
-
93
- ```python
94
- from rest_framework.viewsets import ModelViewSet
95
- from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin
96
-
97
- class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
98
- queryset = Invoice.objects.all()
99
- serializer_class = InvoiceSerializer
100
- lookup_url_kwarg = "id"
101
- lookup_strategies = ("display_id", "uuid")
102
- display_id_prefix = "inv"
103
- ```
104
-
105
- Or with APIView:
106
-
107
- ```python
108
- from rest_framework.views import APIView
109
- from rest_framework.response import Response
110
- from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin
111
-
112
- class InvoiceView(DisplayIDLookupMixin, APIView):
113
- lookup_url_kwarg = "id"
114
- lookup_strategies = ("display_id", "uuid")
115
- display_id_prefix = "inv"
116
-
117
- def get_queryset(self):
118
- return Invoice.objects.all()
119
-
120
- def get(self, request, *args, **kwargs):
121
- invoice = self.get_object()
122
- return Response({"id": str(invoice.id)})
123
- ```
124
-
125
- #### Serializer Field
126
-
127
- Include `display_id` in your API responses:
128
-
129
- ```python
130
- from rest_framework import serializers
131
- from django_display_ids.contrib.rest_framework import DisplayIDField
132
-
133
- class InvoiceSerializer(serializers.Serializer):
134
- id = serializers.UUIDField(read_only=True)
135
- display_id = DisplayIDField()
136
- name = serializers.CharField()
137
-
138
- # Output: {"id": "...", "display_id": "inv_2aUyqjCzEIiEcYMKj7TZtw", ...}
139
- ```
140
-
141
- The field reads `display_id_prefix` from the model. You can override it:
142
-
143
- ```python
144
- display_id = DisplayIDField(prefix="inv") # Use custom prefix
145
- ```
146
-
147
- Prefix must be 1-16 lowercase letters. Invalid prefixes raise `ValueError` at initialization.
148
-
149
- **OpenAPI/drf-spectacular**: When drf-spectacular is installed, the field automatically generates proper schema with prefix-specific examples (e.g., `inv_2aUyqjCzEIiEcYMKj7TZtw`). The prefix is resolved from (in order): field's `prefix=` argument, serializer's `Meta.model.display_id_prefix`, or the view's queryset model.
150
-
151
- #### OpenAPI Parameter Descriptions
152
-
153
- For consistent API documentation, use the provided description helpers:
154
-
155
- ```python
156
- from django_display_ids.contrib.rest_framework import id_param_description
157
- from drf_spectacular.utils import extend_schema, OpenApiParameter
158
- from drf_spectacular.types import OpenApiTypes
159
-
160
- @extend_schema(
161
- parameters=[
162
- OpenApiParameter(
163
- "id",
164
- OpenApiTypes.STR,
165
- OpenApiParameter.PATH,
166
- description=id_param_description("inv"),
167
- # -> "Identifier: display_id (inv_xxx) or UUID"
168
- )
169
- ],
170
- )
171
- class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
172
- ...
173
- ```
174
-
175
- For endpoints that also accept slugs:
176
-
177
- ```python
178
- description=id_param_description("app", with_slug=True)
179
- # -> "Identifier: display_id (app_xxx), UUID, or slug"
180
- ```
181
-
182
- Generic constants are also available:
183
-
184
- ```python
185
- from django_display_ids.contrib.rest_framework import (
186
- ID_PARAM_DESCRIPTION, # "Identifier: display_id (prefix_xxx) or UUID"
187
- ID_PARAM_DESCRIPTION_WITH_SLUG, # "Identifier: display_id (prefix_xxx), UUID, or slug"
188
- )
189
- ```
190
-
191
- #### Deterministic Examples for OpenAPI
192
-
193
- Generate consistent example UUIDs and display IDs for OpenAPI schemas:
194
-
195
- ```python
196
- from django_display_ids import example_uuid, example_display_id
197
-
198
- # Generate deterministic UUID for a prefix
199
- example_uuid("inv")
200
- # -> UUID('a172cedc-ae47-474b-615c-54d510a5d84a')
201
-
202
- # Generate deterministic display ID
203
- example_display_id("inv")
204
- # -> "inv_4ueEO5Nz4X7u9qc3FVHokM"
205
-
206
- # Also works with model classes
207
- example_uuid(Invoice) # Uses Invoice.display_id_prefix
208
- ```
209
-
210
- The same prefix always produces the same example, ensuring consistent documentation across regenerations.
211
-
212
- ### Model Mixin
213
-
214
- Add a `display_id` property to your models:
215
-
216
- ```python
217
- import uuid
218
- from django.db import models
219
- from django_display_ids import DisplayIDMixin
220
-
221
- class Invoice(DisplayIDMixin, models.Model):
222
- display_id_prefix = "inv"
223
- id = models.UUIDField(primary_key=True, default=uuid.uuid4)
224
-
225
- invoice = Invoice.objects.first()
226
- invoice.display_id # -> "inv_2aUyqjCzEIiEcYMKj7TZtw"
227
- ```
228
-
229
- ### Model Manager
230
-
231
- ```python
232
- from django_display_ids import DisplayIDMixin, DisplayIDManager
233
-
234
- class Invoice(DisplayIDMixin, models.Model):
235
- display_id_prefix = "inv"
236
- objects = DisplayIDManager()
237
- id = models.UUIDField(primary_key=True, default=uuid.uuid4)
238
-
239
- # Get by display ID
240
- invoice = Invoice.objects.get_by_display_id("inv_2aUyqjCzEIiEcYMKj7TZtw")
241
-
242
- # Get by any identifier type
243
- invoice = Invoice.objects.get_by_identifier("inv_2aUyqjCzEIiEcYMKj7TZtw")
244
- invoice = Invoice.objects.get_by_identifier("550e8400-e29b-41d4-a716-446655440000")
245
-
246
- # Works with filtered querysets
247
- invoice = Invoice.objects.filter(active=True).get_by_identifier("inv_xxx")
248
- ```
249
-
250
- ### Django Admin
251
-
252
- Enable searching by display ID or raw UUID in the admin:
253
-
254
- ```python
255
- from django.contrib import admin
256
- from django_display_ids import DisplayIDSearchMixin
257
-
258
- @admin.register(Invoice)
259
- class InvoiceAdmin(DisplayIDSearchMixin, admin.ModelAdmin):
260
- list_display = ["id", "display_id", "name", "created"]
261
- search_fields = ["name"] # display_id/UUID search is automatic
262
- ```
263
-
264
- Now you can search by either format in the admin search box:
265
- - `inv_2aUyqjCzEIiEcYMKj7TZtw` (display ID)
266
- - `550e8400-e29b-41d4-a716-446655440000` (raw UUID from logs)
267
-
268
- The mixin automatically detects the UUID field from your model's `uuid_field`
269
- attribute (if using `DisplayIDMixin`), or defaults to `id`. Override with:
270
-
271
- ```python
272
- class InvoiceAdmin(DisplayIDSearchMixin, admin.ModelAdmin):
273
- uuid_field = "uid" # custom UUID field name
274
- ```
275
-
276
- ### Encoding and Decoding
277
-
278
- ```python
279
- import uuid
280
- from django_display_ids import encode_display_id, decode_display_id
281
-
282
- # Create a display ID from a UUID
283
- invoice_id = uuid.uuid4()
284
- display_id = encode_display_id("inv", invoice_id)
285
- # -> "inv_2aUyqjCzEIiEcYMKj7TZtw"
286
-
287
- # Decode back to prefix and UUID
288
- prefix, decoded_uuid = decode_display_id(display_id)
289
- ```
290
-
291
- ### Direct Resolution
292
-
293
- ```python
294
- from django_display_ids import resolve_object
295
-
296
- invoice = resolve_object(
297
- model=Invoice,
298
- value="inv_2aUyqjCzEIiEcYMKj7TZtw",
299
- strategies=("display_id", "uuid", "slug"),
300
- prefix="inv",
301
- )
302
- ```
303
-
304
- ## Identifier Formats
305
-
306
- | Format | Example | Description |
307
- |--------|---------|-------------|
308
- | Display ID | `inv_2aUyqjCzEIiEcYMKj7TZtw` | Prefix + base62-encoded UUID |
309
- | UUID | `550e8400-e29b-41d4-a716-446655440000` | Standard UUID (v4/v7) |
310
- | Slug | `my-invoice-slug` | Human-readable identifier |
311
-
312
- Display ID format:
313
- - Prefix: 1-16 lowercase letters
314
- - Separator: underscore
315
- - Encoded UUID: 22 base62 characters (fixed length)
316
-
317
- ## Lookup Strategies
318
-
319
- Strategies are tried in order. The first successful match is returned.
320
-
321
- | Strategy | Description |
322
- |----------|-------------|
323
- | `display_id` | Decode display ID, lookup by UUID field |
324
- | `uuid` | Parse as UUID, lookup by UUID field |
325
- | `slug` | Lookup by slug field |
326
-
327
- Default: `("display_id", "uuid")`
328
-
329
- The slug strategy is a catch-all, so it should always be last.
330
-
331
- The `display_id` strategy requires a prefix. If no prefix is configured, the strategy is skipped.
332
-
333
- ## Configuration
334
-
335
- ### View/Mixin Attributes
336
-
337
- | Attribute | Default | Description |
338
- |-----------|---------|-------------|
339
- | `lookup_param` / `lookup_url_kwarg` | `"pk"` | URL parameter name |
340
- | `lookup_strategies` | from settings | Strategies to try |
341
- | `display_id_prefix` | from model | Expected prefix (falls back to model's `display_id_prefix`) |
342
- | `uuid_field` | `"id"` | UUID field name on model |
343
- | `slug_field` | `"slug"` | Slug field name on model |
344
-
345
- ### Django Settings (Optional)
346
-
347
- All settings have sensible defaults. Only add this if you need to override them:
348
-
349
- ```python
350
- # settings.py
351
- DISPLAY_IDS = {
352
- "UUID_FIELD": "id", # default
353
- "SLUG_FIELD": "slug", # default
354
- "STRATEGIES": ("display_id", "uuid"), # default
355
- }
356
- ```
357
-
358
- ## Error Handling
359
-
360
- | Exception | When Raised |
361
- |-----------|-------------|
362
- | `InvalidIdentifierError` | Identifier cannot be parsed |
363
- | `UnknownPrefixError` | Display ID prefix doesn't match expected |
364
- | `ObjectNotFoundError` | No matching database record |
365
-
366
- In views, errors are converted to HTTP responses:
367
- - Django CBV: `Http404`
368
- - DRF: `NotFound` (404) or `ParseError` (400)
369
-
370
- ## Requirements
371
-
372
- - Python 3.12+
373
- - Django 4.2+
374
- - Django REST Framework 3.14+ (optional)
375
-
376
- ## Development
377
-
378
- Clone the repository and install dependencies:
379
-
380
- ```bash
381
- git clone https://github.com/josephabrahams/django-display-ids.git
382
- cd django-display-ids
383
- uv sync
384
- ```
385
-
386
- Run tests:
387
-
388
- ```bash
389
- uv run pytest
390
- ```
391
-
392
- Run tests with coverage:
393
-
394
- ```bash
395
- uv run pytest --cov=src/django_display_ids
396
- ```
397
-
398
- Run tests across Python and Django versions:
399
-
400
- ```bash
401
- uvx nox
402
- ```
403
-
404
- Lint and format:
405
-
406
- ```bash
407
- uvx pre-commit run --all-files
408
- ```
409
-
410
- ## Related Projects
411
-
412
- If you need ID generation and storage (custom model fields), consider these alternatives:
413
-
414
- - **[django-prefix-id](https://github.com/jaddison/django-prefix-id)** — PrefixIDField that generates and stores base62-encoded UUIDs
415
- - **[django-spicy-id](https://github.com/mik3y/django-spicy-id)** — Drop-in AutoField replacement that displays numeric IDs as prefixed strings
416
- - **[django-charid-field](https://github.com/yunojuno/django-charid-field)** — CharField wrapper supporting cuid, ksuid, ulid, and other generators
417
-
418
- **django-display-ids** takes a different approach: it works with your existing UUID fields and handles resolution only. No migrations, no schema changes — just add the mixin to your views.
419
-
420
- ## License
421
-
422
- ISC
@@ -1,21 +0,0 @@
1
- django_display_ids/__init__.py,sha256=7_rl0cA0LWGIbhg5SbnWAZfKAWyXEpA5XzKDmPnoM8U,2650
2
- django_display_ids/admin.py,sha256=uRyPH3q5e9D5oMxs6PtCHq0syBXs--i1alRoe-EcIJo,2935
3
- django_display_ids/conf.py,sha256=qTsCzKeNBdJpEVeEkx2kFeWHBFa_NZwV_tpt-UTyRR0,1132
4
- django_display_ids/contrib/__init__.py,sha256=sxGJK8Whb6cL7RqACqGRYrIZvaMwP3l6dYk3mIYWzDY,62
5
- django_display_ids/contrib/drf_spectacular/__init__.py,sha256=uPA1foQ8BjyHX3SS9Ta9yI7wp0y3sLdQ4EPOx4KIBlI,3770
6
- django_display_ids/contrib/rest_framework/__init__.py,sha256=Xun6zMhCZzQJZQ7ywvBYoKhTJIZEV84AntrbSWfBjYI,1567
7
- django_display_ids/contrib/rest_framework/serializers.py,sha256=zsYDmXVbra5gUkOnupwJd_vEQnLbgXVWNwehHje2GTs,3991
8
- django_display_ids/contrib/rest_framework/views.py,sha256=mu-twvdRbJedzSfLWVNn2BazFpAQzwRB5Eq3w2bxvvs,6056
9
- django_display_ids/encoding.py,sha256=csIwUZaQKSOLwRU6-DWGTNGvSxmroyK0Yt7TBCo0AFE,2945
10
- django_display_ids/examples.py,sha256=rrZgL-EoWz0mC32T-RNWCo45Y8_BXV_6r_5tRIz77gs,2677
11
- django_display_ids/exceptions.py,sha256=nmyRfpsqVvz226Zcu_QANwr8MudbfoX09mAgOCwuPuQ,3022
12
- django_display_ids/managers.py,sha256=EFvlQxsSFXeM8TVvV4NZKeKMC7QB3C0zYGgZ6bvSr4k,6884
13
- django_display_ids/models.py,sha256=_IXxaFlVw2MqobQUw4Cy4rB66LsTz6kiI5YpoSpnkCY,4156
14
- django_display_ids/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- django_display_ids/resolver.py,sha256=oCoA6jbGCFS8SMrkfD_oSSBQNrSxnxdooK5j933eA9Y,2494
16
- django_display_ids/strategies.py,sha256=Rq00-AW_FB8-K04u2oBK5J6kPiYgsE3TdYlLyK_zro0,4436
17
- django_display_ids/typing.py,sha256=2O3kT7XKkiE7WI9A5KkILPM-Zi7-zCy5gVvXQL_J2mI,478
18
- django_display_ids/views.py,sha256=sLsJm8Tpe3Qk1gOLcDzfpazxuaVqTCAdgVIXOONFnKQ,5096
19
- django_display_ids-0.1.4.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
20
- django_display_ids-0.1.4.dist-info/METADATA,sha256=46ob0brsZSBW7QFrmISYvNkD7mQGu3NkIXRe3ueae6g,12356
21
- django_display_ids-0.1.4.dist-info/RECORD,,