django-display-ids 0.4.0__py3-none-any.whl → 0.4.1__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.
- django_display_ids/converters.py +8 -9
- django_display_ids/managers.py +42 -12
- django_display_ids/resolver.py +14 -5
- {django_display_ids-0.4.0.dist-info → django_display_ids-0.4.1.dist-info}/METADATA +2 -1
- {django_display_ids-0.4.0.dist-info → django_display_ids-0.4.1.dist-info}/RECORD +6 -6
- {django_display_ids-0.4.0.dist-info → django_display_ids-0.4.1.dist-info}/WHEEL +1 -1
django_display_ids/converters.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from .conf import SLUG_REGEX
|
|
5
|
+
from .conf import SLUG_REGEX, get_setting
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"DISPLAY_ID_REGEX",
|
|
@@ -19,6 +19,9 @@ __all__ = [
|
|
|
19
19
|
DISPLAY_ID_REGEX = r"[a-z]{1,16}_[0-9A-Za-z]{22}"
|
|
20
20
|
UUID_REGEX = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
21
21
|
|
|
22
|
+
# Slug regex from settings (respects DISPLAY_IDS["SLUG_REGEX"] Django setting)
|
|
23
|
+
_SLUG_REGEX: str = str(get_setting("SLUG_REGEX"))
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class BaseConverter:
|
|
24
27
|
"""Base class for URL path converters with pass-through conversion."""
|
|
@@ -78,7 +81,7 @@ class DisplayIDOrSlugConverter(BaseConverter):
|
|
|
78
81
|
|
|
79
82
|
Matches either format:
|
|
80
83
|
- Display ID: {prefix}_{base62}
|
|
81
|
-
- Slug:
|
|
84
|
+
- Slug: matches DISPLAY_IDS["SLUG_REGEX"] setting (default: [-a-zA-Z0-9_]+)
|
|
82
85
|
|
|
83
86
|
Example:
|
|
84
87
|
from django.urls import path, register_converter
|
|
@@ -91,7 +94,7 @@ class DisplayIDOrSlugConverter(BaseConverter):
|
|
|
91
94
|
]
|
|
92
95
|
"""
|
|
93
96
|
|
|
94
|
-
regex = rf"(?:{DISPLAY_ID_REGEX}|{
|
|
97
|
+
regex = rf"(?:{DISPLAY_ID_REGEX}|{_SLUG_REGEX})"
|
|
95
98
|
|
|
96
99
|
|
|
97
100
|
class DisplayIDOrUUIDOrSlugConverter(BaseConverter):
|
|
@@ -100,7 +103,7 @@ class DisplayIDOrUUIDOrSlugConverter(BaseConverter):
|
|
|
100
103
|
Matches any of:
|
|
101
104
|
- Display ID: {prefix}_{base62}
|
|
102
105
|
- UUID: hyphenated (e.g., 550e8400-e29b-41d4-a716-446655440000)
|
|
103
|
-
- Slug:
|
|
106
|
+
- Slug: matches DISPLAY_IDS["SLUG_REGEX"] setting (default: [-a-zA-Z0-9_]+)
|
|
104
107
|
|
|
105
108
|
Example:
|
|
106
109
|
from django.urls import path, register_converter
|
|
@@ -113,7 +116,7 @@ class DisplayIDOrUUIDOrSlugConverter(BaseConverter):
|
|
|
113
116
|
]
|
|
114
117
|
"""
|
|
115
118
|
|
|
116
|
-
regex = rf"(?:{DISPLAY_ID_REGEX}|{UUID_REGEX}|{
|
|
119
|
+
regex = rf"(?:{DISPLAY_ID_REGEX}|{UUID_REGEX}|{_SLUG_REGEX})"
|
|
117
120
|
|
|
118
121
|
|
|
119
122
|
def make_display_id_or_slug_converter(
|
|
@@ -140,8 +143,6 @@ def make_display_id_or_slug_converter(
|
|
|
140
143
|
path("products/<display_id_or_slug:id>/", ProductDetailView.as_view()),
|
|
141
144
|
]
|
|
142
145
|
"""
|
|
143
|
-
from .conf import get_setting
|
|
144
|
-
|
|
145
146
|
pattern = slug_regex if slug_regex is not None else get_setting("SLUG_REGEX")
|
|
146
147
|
|
|
147
148
|
class CustomDisplayIDOrSlugConverter(DisplayIDOrSlugConverter):
|
|
@@ -176,8 +177,6 @@ def make_display_id_or_uuid_or_slug_converter(
|
|
|
176
177
|
path("products/<identifier:id>/", ProductDetailView.as_view()),
|
|
177
178
|
]
|
|
178
179
|
"""
|
|
179
|
-
from .conf import get_setting
|
|
180
|
-
|
|
181
180
|
pattern = slug_regex if slug_regex is not None else get_setting("SLUG_REGEX")
|
|
182
181
|
|
|
183
182
|
class CustomDisplayIDOrUUIDOrSlugConverter(DisplayIDOrUUIDOrSlugConverter):
|
django_display_ids/managers.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import uuid
|
|
5
6
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
7
|
|
|
7
8
|
from django.db import models
|
|
@@ -51,14 +52,15 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
51
52
|
|
|
52
53
|
def get_by_display_id(
|
|
53
54
|
self,
|
|
54
|
-
value: str,
|
|
55
|
+
value: str | uuid.UUID,
|
|
55
56
|
*,
|
|
56
57
|
prefix: str | None = None,
|
|
57
58
|
) -> M:
|
|
58
59
|
"""Get an object by its display ID.
|
|
59
60
|
|
|
60
61
|
Args:
|
|
61
|
-
value: The display ID string (e.g., "inv_1a2B3c4D5e6F7g8H")
|
|
62
|
+
value: The display ID string (e.g., "inv_1a2B3c4D5e6F7g8H"),
|
|
63
|
+
or a UUID instance for direct UUID lookup.
|
|
62
64
|
prefix: Expected prefix for validation. If None, uses model's prefix.
|
|
63
65
|
|
|
64
66
|
Returns:
|
|
@@ -70,9 +72,19 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
70
72
|
UnknownPrefixError: If the prefix doesn't match expected.
|
|
71
73
|
ObjectNotFoundError: If no matching object exists.
|
|
72
74
|
"""
|
|
73
|
-
# Get model config
|
|
74
75
|
model = self.model
|
|
75
76
|
uuid_field = self._get_uuid_field()
|
|
77
|
+
|
|
78
|
+
# UUID objects skip display ID parsing entirely
|
|
79
|
+
if isinstance(value, uuid.UUID):
|
|
80
|
+
try:
|
|
81
|
+
return self.get(**{uuid_field: value})
|
|
82
|
+
except model.DoesNotExist: # type: ignore[attr-defined]
|
|
83
|
+
raise ObjectNotFoundError(
|
|
84
|
+
str(value), model_name=model.__name__
|
|
85
|
+
) from None
|
|
86
|
+
|
|
87
|
+
# Get model config
|
|
76
88
|
expected_prefix = prefix or self._get_model_prefix()
|
|
77
89
|
|
|
78
90
|
# Require a prefix for display ID lookups
|
|
@@ -99,7 +111,7 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
99
111
|
|
|
100
112
|
def get_by_identifier(
|
|
101
113
|
self,
|
|
102
|
-
value: str,
|
|
114
|
+
value: str | uuid.UUID,
|
|
103
115
|
*,
|
|
104
116
|
strategies: tuple[StrategyName, ...] | None = None,
|
|
105
117
|
prefix: str | None = None,
|
|
@@ -109,7 +121,8 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
109
121
|
Tries each strategy in order and returns the first match.
|
|
110
122
|
|
|
111
123
|
Args:
|
|
112
|
-
value: The identifier string (display ID, UUID, or slug)
|
|
124
|
+
value: The identifier string (display ID, UUID, or slug),
|
|
125
|
+
or a UUID instance for direct UUID lookup.
|
|
113
126
|
strategies: Strategies to try. Defaults to settings.
|
|
114
127
|
prefix: Expected display ID prefix for validation.
|
|
115
128
|
|
|
@@ -124,6 +137,16 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
124
137
|
"""
|
|
125
138
|
model = self.model
|
|
126
139
|
uuid_field = self._get_uuid_field()
|
|
140
|
+
|
|
141
|
+
# UUID objects skip strategy parsing entirely
|
|
142
|
+
if isinstance(value, uuid.UUID):
|
|
143
|
+
try:
|
|
144
|
+
return self.get(**{uuid_field: value})
|
|
145
|
+
except model.DoesNotExist: # type: ignore[attr-defined]
|
|
146
|
+
raise ObjectNotFoundError(
|
|
147
|
+
str(value), model_name=model.__name__
|
|
148
|
+
) from None
|
|
149
|
+
|
|
127
150
|
slug_field = self._get_slug_field()
|
|
128
151
|
expected_prefix = prefix or self._get_model_prefix()
|
|
129
152
|
lookup_strategies = strategies or self._get_strategies()
|
|
@@ -144,14 +167,14 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
144
167
|
try:
|
|
145
168
|
return self.get(**lookup)
|
|
146
169
|
except model.DoesNotExist: # type: ignore[attr-defined]
|
|
147
|
-
raise ObjectNotFoundError(value, model_name=model.__name__) from None
|
|
170
|
+
raise ObjectNotFoundError(str(value), model_name=model.__name__) from None
|
|
148
171
|
except model.MultipleObjectsReturned: # type: ignore[attr-defined]
|
|
149
172
|
count = self.filter(**lookup).count()
|
|
150
|
-
raise AmbiguousIdentifierError(value, count) from None
|
|
173
|
+
raise AmbiguousIdentifierError(str(value), count) from None
|
|
151
174
|
|
|
152
175
|
def get_by_identifiers(
|
|
153
176
|
self,
|
|
154
|
-
values: Sequence[str],
|
|
177
|
+
values: Sequence[str | uuid.UUID],
|
|
155
178
|
*,
|
|
156
179
|
strategies: tuple[StrategyName, ...] | None = None,
|
|
157
180
|
prefix: str | None = None,
|
|
@@ -162,7 +185,8 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
162
185
|
then executes a single database query using `__in` lookups.
|
|
163
186
|
|
|
164
187
|
Args:
|
|
165
|
-
values: A sequence of identifier strings (display IDs, UUIDs, or slugs)
|
|
188
|
+
values: A sequence of identifier strings (display IDs, UUIDs, or slugs)
|
|
189
|
+
or UUID instances. UUID instances skip strategy parsing.
|
|
166
190
|
strategies: Strategies to try. Defaults to settings.
|
|
167
191
|
prefix: Expected display ID prefix for validation.
|
|
168
192
|
|
|
@@ -178,6 +202,7 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
178
202
|
'inv_2aUyqjCzEIiEcYMKj7TZtw',
|
|
179
203
|
'inv_7kN3xPqRmLwYvTzJ5HfUaB',
|
|
180
204
|
'550e8400-e29b-41d4-a716-446655440000',
|
|
205
|
+
uuid.UUID('550e8400-e29b-41d4-a716-446655440000'),
|
|
181
206
|
])
|
|
182
207
|
"""
|
|
183
208
|
if not values:
|
|
@@ -193,6 +218,11 @@ class DisplayIDQuerySet(models.QuerySet[M]):
|
|
|
193
218
|
slugs: list[str] = []
|
|
194
219
|
|
|
195
220
|
for value in values:
|
|
221
|
+
# UUID objects skip strategy parsing entirely
|
|
222
|
+
if isinstance(value, uuid.UUID):
|
|
223
|
+
uuids.append(value)
|
|
224
|
+
continue
|
|
225
|
+
|
|
196
226
|
result = parse_identifier(
|
|
197
227
|
value, lookup_strategies, expected_prefix=expected_prefix
|
|
198
228
|
)
|
|
@@ -253,7 +283,7 @@ class DisplayIDManager(models.Manager[M]):
|
|
|
253
283
|
|
|
254
284
|
def get_by_display_id(
|
|
255
285
|
self,
|
|
256
|
-
value: str,
|
|
286
|
+
value: str | uuid.UUID,
|
|
257
287
|
*,
|
|
258
288
|
prefix: str | None = None,
|
|
259
289
|
) -> M:
|
|
@@ -265,7 +295,7 @@ class DisplayIDManager(models.Manager[M]):
|
|
|
265
295
|
|
|
266
296
|
def get_by_identifier(
|
|
267
297
|
self,
|
|
268
|
-
value: str,
|
|
298
|
+
value: str | uuid.UUID,
|
|
269
299
|
*,
|
|
270
300
|
strategies: tuple[StrategyName, ...] | None = None,
|
|
271
301
|
prefix: str | None = None,
|
|
@@ -280,7 +310,7 @@ class DisplayIDManager(models.Manager[M]):
|
|
|
280
310
|
|
|
281
311
|
def get_by_identifiers(
|
|
282
312
|
self,
|
|
283
|
-
values: Sequence[str],
|
|
313
|
+
values: Sequence[str | uuid.UUID],
|
|
284
314
|
*,
|
|
285
315
|
strategies: tuple[StrategyName, ...] | None = None,
|
|
286
316
|
prefix: str | None = None,
|
django_display_ids/resolver.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import uuid
|
|
5
6
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
7
|
|
|
7
8
|
from django.db import models
|
|
@@ -23,7 +24,7 @@ M = TypeVar("M", bound=models.Model)
|
|
|
23
24
|
def resolve_object(
|
|
24
25
|
*,
|
|
25
26
|
model: type[M],
|
|
26
|
-
value: str,
|
|
27
|
+
value: str | uuid.UUID,
|
|
27
28
|
strategies: tuple[StrategyName, ...] = DEFAULT_STRATEGIES,
|
|
28
29
|
prefix: str | None = None,
|
|
29
30
|
uuid_field: str = "id",
|
|
@@ -36,7 +37,8 @@ def resolve_object(
|
|
|
36
37
|
|
|
37
38
|
Args:
|
|
38
39
|
model: The Django model class.
|
|
39
|
-
value: The identifier string (UUID, display ID, or slug)
|
|
40
|
+
value: The identifier string (UUID, display ID, or slug),
|
|
41
|
+
or a UUID instance for direct UUID lookup.
|
|
40
42
|
strategies: Tuple of strategy names to try in order.
|
|
41
43
|
prefix: Expected display ID prefix (for validation).
|
|
42
44
|
uuid_field: Name of the UUID field on the model.
|
|
@@ -53,9 +55,6 @@ def resolve_object(
|
|
|
53
55
|
AmbiguousIdentifierError: If multiple objects match (slug lookup).
|
|
54
56
|
TypeError: If queryset is not for the specified model.
|
|
55
57
|
"""
|
|
56
|
-
# Parse the identifier to determine type
|
|
57
|
-
result = parse_identifier(value, strategies, expected_prefix=prefix)
|
|
58
|
-
|
|
59
58
|
# Get the base queryset
|
|
60
59
|
if queryset is not None:
|
|
61
60
|
if queryset.model is not model:
|
|
@@ -67,6 +66,16 @@ def resolve_object(
|
|
|
67
66
|
else:
|
|
68
67
|
qs = model._default_manager.all()
|
|
69
68
|
|
|
69
|
+
# UUID objects skip strategy parsing entirely
|
|
70
|
+
if isinstance(value, uuid.UUID):
|
|
71
|
+
try:
|
|
72
|
+
return qs.get(**{uuid_field: value})
|
|
73
|
+
except model.DoesNotExist: # type: ignore[attr-defined]
|
|
74
|
+
raise ObjectNotFoundError(str(value), model_name=model.__name__) from None
|
|
75
|
+
|
|
76
|
+
# Parse the identifier to determine type
|
|
77
|
+
result = parse_identifier(value, strategies, expected_prefix=prefix)
|
|
78
|
+
|
|
70
79
|
# Build the lookup based on strategy
|
|
71
80
|
lookup: dict[str, Any]
|
|
72
81
|
if result.strategy in ("uuid", "display_id"):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-display-ids
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.
|
|
5
5
|
Keywords: django,stripe,uuid,base62,prefixed-id,drf,shortuuid,nanoid,ulid
|
|
6
6
|
License: MIT
|
|
@@ -19,6 +19,7 @@ Requires-Dist: django>=4.2
|
|
|
19
19
|
Requires-Python: >=3.12
|
|
20
20
|
Project-URL: Documentation, https://django-display-ids.readthedocs.io/
|
|
21
21
|
Project-URL: Repository, https://github.com/josephabrahams/django-display-ids
|
|
22
|
+
Project-URL: Changelog, https://github.com/josephabrahams/django-display-ids/blob/main/CHANGELOG.md
|
|
22
23
|
Description-Content-Type: text/markdown
|
|
23
24
|
|
|
24
25
|
# django-display-ids
|
|
@@ -7,19 +7,19 @@ django_display_ids/contrib/drf_spectacular/__init__.py,sha256=9swSJge_dQldC4AZMk
|
|
|
7
7
|
django_display_ids/contrib/rest_framework/__init__.py,sha256=pKV6BVB7k0dJm29C7XMr0M-wK2lXPUgW8Hf4BZHHwuE,198
|
|
8
8
|
django_display_ids/contrib/rest_framework/serializers.py,sha256=Jp-z7qHafxkGNYv30YC9rrqLt936SrhONJv3rqfQaC0,4049
|
|
9
9
|
django_display_ids/contrib/rest_framework/views.py,sha256=I6jpsdpM1sRKrPBVyrjypsKgFyUdu6WljGcffHXDiCw,5961
|
|
10
|
-
django_display_ids/converters.py,sha256=
|
|
10
|
+
django_display_ids/converters.py,sha256=KGD5FYWkuXztO-6TvTtPwMP7xbnAXAaYuk9LvIpxkBM,5918
|
|
11
11
|
django_display_ids/encoding.py,sha256=csIwUZaQKSOLwRU6-DWGTNGvSxmroyK0Yt7TBCo0AFE,2945
|
|
12
12
|
django_display_ids/examples.py,sha256=gap5NNPTmE7B5uxiYKoMoK8G-OEtL1Ek0W039l6oJ9I,2689
|
|
13
13
|
django_display_ids/exceptions.py,sha256=ATIW6SLM9zQ0s-26c3qdQfnPDbuiMpMZK6fxdyQN0tA,3085
|
|
14
|
-
django_display_ids/managers.py,sha256=
|
|
14
|
+
django_display_ids/managers.py,sha256=I7qY1V64lDPplhE56ueARICFOHEQqCJhme6LQfBWo_o,11031
|
|
15
15
|
django_display_ids/models.py,sha256=_hmgAR4HC3I-5wU2DND6uNLjEllu1Y9eaXeBQ9dWMNI,4313
|
|
16
16
|
django_display_ids/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
django_display_ids/resolver.py,sha256=
|
|
17
|
+
django_display_ids/resolver.py,sha256=YhgfkBEVKFsAA9UB66cbyND46sWYWBHPqOJy-yQWstA,3229
|
|
18
18
|
django_display_ids/strategies.py,sha256=Rq00-AW_FB8-K04u2oBK5J6kPiYgsE3TdYlLyK_zro0,4436
|
|
19
19
|
django_display_ids/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
django_display_ids/templatetags/display_ids.py,sha256=4KHE8r8mgSKb7LgIuXJaJB_3UGrzRZvTdLqSCYQtb5I,1157
|
|
21
21
|
django_display_ids/typing.py,sha256=2O3kT7XKkiE7WI9A5KkILPM-Zi7-zCy5gVvXQL_J2mI,478
|
|
22
22
|
django_display_ids/views.py,sha256=uAXQJryWQNnTSHIjTLhisrYnNiaTo1pLzkUdvYyOLRs,4992
|
|
23
|
-
django_display_ids-0.4.
|
|
24
|
-
django_display_ids-0.4.
|
|
25
|
-
django_display_ids-0.4.
|
|
23
|
+
django_display_ids-0.4.1.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
|
|
24
|
+
django_display_ids-0.4.1.dist-info/METADATA,sha256=irrOfygrirWeDRa2mCNHvsnKpb6VJRd_8Ty1orTkRGg,5359
|
|
25
|
+
django_display_ids-0.4.1.dist-info/RECORD,,
|