django-display-ids 0.1.4__py3-none-any.whl → 0.2.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.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.3
2
+ Name: django-display-ids
3
+ Version: 0.2.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: 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: 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
+
30
+ Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
31
+
32
+ **Documentation**: [django-display-ids.readthedocs.io](https://django-display-ids.readthedocs.io/)
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install django-display-ids
38
+ ```
39
+
40
+ No `INSTALLED_APPS` entry required.
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from django.views.generic import DetailView
46
+ from django_display_ids import DisplayIDObjectMixin
47
+
48
+ class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
49
+ model = Invoice
50
+ lookup_param = "id"
51
+ lookup_strategies = ("display_id", "uuid")
52
+ display_id_prefix = "inv"
53
+ ```
54
+
55
+ ```python
56
+ # urls.py
57
+ urlpatterns = [
58
+ path("invoices/<str:id>/", InvoiceDetailView.as_view()),
59
+ ]
60
+ ```
61
+
62
+ Now your view accepts:
63
+ - `inv_2aUyqjCzEIiEcYMKj7TZtw` (display ID)
64
+ - `550e8400-e29b-41d4-a716-446655440000` (UUID)
65
+
66
+ ## Features
67
+
68
+ - **Multiple identifier formats**: display ID (`prefix_base62uuid`), UUID (v4/v7), slug
69
+ - **Framework support**: Django CBVs and Django REST Framework
70
+ - **Zero model changes required**: Works with any existing UUID field
71
+ - **OpenAPI integration**: Automatic schema generation with drf-spectacular
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ git clone https://github.com/josephabrahams/django-display-ids.git
77
+ cd django-display-ids
78
+ uv sync
79
+ ```
80
+
81
+ Run tests:
82
+
83
+ ```bash
84
+ uv run pytest
85
+ ```
86
+
87
+ Run tests across Python and Django versions:
88
+
89
+ ```bash
90
+ uvx nox
91
+ ```
92
+
93
+ Lint and format:
94
+
95
+ ```bash
96
+ uvx pre-commit run --all-files
97
+ ```
98
+
99
+ ## Related Projects
100
+
101
+ If you need ID generation and storage (custom model fields), consider:
102
+
103
+ - **[django-prefix-id](https://github.com/jaddison/django-prefix-id)** — PrefixIDField that generates and stores base62-encoded UUIDs
104
+ - **[django-spicy-id](https://github.com/mik3y/django-spicy-id)** — Drop-in AutoField replacement
105
+ - **[django-charid-field](https://github.com/yunojuno/django-charid-field)** — CharField wrapper supporting cuid, ksuid, ulid
106
+
107
+ **django-display-ids** works with existing UUID fields and handles resolution only — no migrations required.
108
+
109
+ ## License
110
+
111
+ ISC
@@ -16,6 +16,6 @@ django_display_ids/resolver.py,sha256=oCoA6jbGCFS8SMrkfD_oSSBQNrSxnxdooK5j933eA9
16
16
  django_display_ids/strategies.py,sha256=Rq00-AW_FB8-K04u2oBK5J6kPiYgsE3TdYlLyK_zro0,4436
17
17
  django_display_ids/typing.py,sha256=2O3kT7XKkiE7WI9A5KkILPM-Zi7-zCy5gVvXQL_J2mI,478
18
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,,
19
+ django_display_ids-0.2.0.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
20
+ django_display_ids-0.2.0.dist-info/METADATA,sha256=cXxwTVjdSXtYv0ghEid7n-udEjrdZrZWhbEWSkvFr6M,3342
21
+ django_display_ids-0.2.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.27
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