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.
@@ -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: Django's default slug pattern [-a-zA-Z0-9_]+
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}|{SLUG_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: Django's default slug pattern [-a-zA-Z0-9_]+
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}|{SLUG_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):
@@ -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,
@@ -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.0
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=ElwrfA7DXiadSZ-Sjvl6ZALgH7tfEZ-tLI7UdE6MsAs,5797
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=PymcK4BZL6UsUOtoloHP34MCRNmvNHSKEcOImhZxGag,9779
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=ZlDVoxX0PmVf0MSwPyiNNwQVzdqJGDGE8fm2iyV7QjE,2848
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.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
24
- django_display_ids-0.4.0.dist-info/METADATA,sha256=La5mR7VliD_sGMKjFazUtnrgPFPQ1anLrsxWou9_ej8,5259
25
- django_display_ids-0.4.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.28
2
+ Generator: uv 0.9.30
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any