django-display-ids 0.3.1__py3-none-any.whl → 0.3.2__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.
@@ -26,7 +26,14 @@ Example:
26
26
  from typing import Any
27
27
 
28
28
  from .admin import DisplayIDSearchMixin
29
- from .converters import DisplayIDConverter, DisplayIDOrUUIDConverter, UUIDConverter
29
+ from .converters import (
30
+ DisplayIDConverter,
31
+ DisplayIDOrSlugConverter,
32
+ DisplayIDOrUUIDConverter,
33
+ DisplayIDOrUUIDOrSlugConverter,
34
+ make_display_id_or_slug_converter,
35
+ make_display_id_or_uuid_or_slug_converter,
36
+ )
30
37
  from .encoding import (
31
38
  decode_display_id,
32
39
  decode_uuid,
@@ -69,8 +76,11 @@ def __getattr__(name: str) -> Any:
69
76
  __all__ = [ # noqa: RUF022 - keep categorized order for readability
70
77
  # URL converters
71
78
  "DisplayIDConverter",
72
- "UUIDConverter",
79
+ "DisplayIDOrSlugConverter",
73
80
  "DisplayIDOrUUIDConverter",
81
+ "DisplayIDOrUUIDOrSlugConverter",
82
+ "make_display_id_or_slug_converter",
83
+ "make_display_id_or_uuid_or_slug_converter",
74
84
  # Encoding
75
85
  "encode_uuid",
76
86
  "decode_uuid",
@@ -14,19 +14,25 @@ from __future__ import annotations
14
14
  from typing import TYPE_CHECKING
15
15
 
16
16
  from django.conf import settings
17
+ from django.urls.converters import SlugConverter
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from .typing import StrategyName
20
21
 
21
22
  __all__ = [
22
23
  "DEFAULTS",
24
+ "SLUG_REGEX",
23
25
  "get_setting",
24
26
  ]
25
27
 
28
+ # Django's default slug regex pattern
29
+ SLUG_REGEX: str = SlugConverter.regex
30
+
26
31
  DEFAULTS: dict[str, str | tuple[str, ...]] = {
27
32
  "UUID_FIELD": "id",
28
33
  "SLUG_FIELD": "slug",
29
34
  "STRATEGIES": ("display_id", "uuid"),
35
+ "SLUG_REGEX": SLUG_REGEX,
30
36
  }
31
37
 
32
38
 
@@ -2,14 +2,37 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from .conf import SLUG_REGEX
6
+
5
7
  __all__ = [
8
+ "DISPLAY_ID_REGEX",
9
+ "SLUG_REGEX",
6
10
  "DisplayIDConverter",
11
+ "DisplayIDOrSlugConverter",
7
12
  "DisplayIDOrUUIDConverter",
8
- "UUIDConverter",
13
+ "DisplayIDOrUUIDOrSlugConverter",
14
+ "make_display_id_or_slug_converter",
15
+ "make_display_id_or_uuid_or_slug_converter",
9
16
  ]
10
17
 
18
+ # Regex pattern constants
19
+ DISPLAY_ID_REGEX = r"[a-z]{1,16}_[0-9A-Za-z]{22}"
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
+
22
+
23
+ class BaseConverter:
24
+ """Base class for URL path converters with pass-through conversion."""
25
+
26
+ def to_python(self, value: str) -> str:
27
+ """Convert the URL value to a Python object."""
28
+ return value
29
+
30
+ def to_url(self, value: str) -> str:
31
+ """Convert a Python object to a URL string."""
32
+ return value
33
+
11
34
 
12
- class DisplayIDConverter:
35
+ class DisplayIDConverter(BaseConverter):
13
36
  """Path converter for display IDs.
14
37
 
15
38
  Matches the format: {prefix}_{base62} where prefix is 1-16 lowercase
@@ -26,86 +49,138 @@ class DisplayIDConverter:
26
49
  ]
27
50
  """
28
51
 
29
- regex = r"[a-z]{1,16}_[0-9A-Za-z]{22}"
52
+ regex = DISPLAY_ID_REGEX
30
53
 
31
- def to_python(self, value: str) -> str:
32
- """Convert the URL value to a Python object."""
33
- return value
34
54
 
35
- def to_url(self, value: str) -> str:
36
- """Convert a Python object to a URL string."""
37
- return value
55
+ class DisplayIDOrUUIDConverter(BaseConverter):
56
+ """Path converter for display IDs or UUIDs.
57
+
58
+ Matches either format:
59
+ - Display ID: {prefix}_{base62}
60
+ - UUID: hyphenated (e.g., 550e8400-e29b-41d4-a716-446655440000)
61
+
62
+ Example:
63
+ from django.urls import path, register_converter
64
+ from django_display_ids.converters import DisplayIDOrUUIDConverter
65
+
66
+ register_converter(DisplayIDOrUUIDConverter, "display_id_or_uuid")
67
+
68
+ urlpatterns = [
69
+ path("invoices/<display_id_or_uuid:id>/", InvoiceDetailView.as_view()),
70
+ ]
71
+ """
72
+
73
+ regex = rf"(?:{DISPLAY_ID_REGEX}|{UUID_REGEX})"
74
+
75
+
76
+ class DisplayIDOrSlugConverter(BaseConverter):
77
+ """Path converter for display IDs or slugs.
78
+
79
+ Matches either format:
80
+ - Display ID: {prefix}_{base62}
81
+ - Slug: Django's default slug pattern [-a-zA-Z0-9_]+
82
+
83
+ Example:
84
+ from django.urls import path, register_converter
85
+ from django_display_ids.converters import DisplayIDOrSlugConverter
86
+
87
+ register_converter(DisplayIDOrSlugConverter, "display_id_or_slug")
88
+
89
+ urlpatterns = [
90
+ path("products/<display_id_or_slug:id>/", ProductDetailView.as_view()),
91
+ ]
92
+ """
38
93
 
94
+ regex = rf"(?:{DISPLAY_ID_REGEX}|{SLUG_REGEX})"
39
95
 
40
- class UUIDConverter:
41
- """Path converter for UUIDs.
42
96
 
43
- Matches UUIDs in both hyphenated and unhyphenated formats:
44
- - 550e8400-e29b-41d4-a716-446655440000 (hyphenated)
45
- - 550e8400e29b41d4a716446655440000 (unhyphenated)
97
+ class DisplayIDOrUUIDOrSlugConverter(BaseConverter):
98
+ """Path converter for display IDs, UUIDs, or slugs.
99
+
100
+ Matches any of:
101
+ - Display ID: {prefix}_{base62}
102
+ - UUID: hyphenated (e.g., 550e8400-e29b-41d4-a716-446655440000)
103
+ - Slug: Django's default slug pattern [-a-zA-Z0-9_]+
46
104
 
47
105
  Example:
48
106
  from django.urls import path, register_converter
49
- from django_display_ids.converters import UUIDConverter
107
+ from django_display_ids.converters import DisplayIDOrUUIDOrSlugConverter
50
108
 
51
- register_converter(UUIDConverter, "uuid")
109
+ register_converter(DisplayIDOrUUIDOrSlugConverter, "identifier")
52
110
 
53
111
  urlpatterns = [
54
- path("invoices/<uuid:id>/", InvoiceDetailView.as_view()),
112
+ path("products/<identifier:id>/", ProductDetailView.as_view()),
55
113
  ]
114
+ """
56
115
 
57
- Note:
58
- Django's built-in UUIDConverter only accepts hyphenated UUIDs.
59
- This converter is more permissive.
116
+ regex = rf"(?:{DISPLAY_ID_REGEX}|{UUID_REGEX}|{SLUG_REGEX})"
117
+
118
+
119
+ def make_display_id_or_slug_converter(
120
+ slug_regex: str | None = None,
121
+ ) -> type[DisplayIDOrSlugConverter]:
122
+ """Create a DisplayIDOrSlugConverter with a custom slug regex.
123
+
124
+ Args:
125
+ slug_regex: Custom slug regex pattern. If None, uses the
126
+ DISPLAY_IDS["SLUG_REGEX"] setting (defaults to Django's pattern).
127
+
128
+ Returns:
129
+ A DisplayIDOrSlugConverter subclass with the custom regex.
130
+
131
+ Example:
132
+ from django.urls import path, register_converter
133
+ from django_display_ids.converters import make_display_id_or_slug_converter
134
+
135
+ # Lowercase slugs only
136
+ LowercaseConverter = make_display_id_or_slug_converter(r"[a-z0-9-]+")
137
+ register_converter(LowercaseConverter, "display_id_or_slug")
138
+
139
+ urlpatterns = [
140
+ path("products/<display_id_or_slug:id>/", ProductDetailView.as_view()),
141
+ ]
60
142
  """
143
+ from .conf import get_setting
61
144
 
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
- )
145
+ pattern = slug_regex if slug_regex is not None else get_setting("SLUG_REGEX")
68
146
 
69
- def to_python(self, value: str) -> str:
70
- """Convert the URL value to a Python object."""
71
- return value
147
+ class CustomDisplayIDOrSlugConverter(DisplayIDOrSlugConverter):
148
+ regex = rf"(?:{DISPLAY_ID_REGEX}|{pattern})"
72
149
 
73
- def to_url(self, value: str) -> str:
74
- """Convert a Python object to a URL string."""
75
- return value
150
+ return CustomDisplayIDOrSlugConverter
76
151
 
77
152
 
78
- class DisplayIDOrUUIDConverter:
79
- """Path converter for display IDs or UUIDs.
153
+ def make_display_id_or_uuid_or_slug_converter(
154
+ slug_regex: str | None = None,
155
+ ) -> type[DisplayIDOrUUIDOrSlugConverter]:
156
+ """Create a DisplayIDOrUUIDOrSlugConverter with a custom slug regex.
80
157
 
81
- Matches either format:
82
- - Display ID: {prefix}_{base62}
83
- - UUID: hyphenated or unhyphenated
158
+ Args:
159
+ slug_regex: Custom slug regex pattern. If None, uses the
160
+ DISPLAY_IDS["SLUG_REGEX"] setting (defaults to Django's pattern).
161
+
162
+ Returns:
163
+ A DisplayIDOrUUIDOrSlugConverter subclass with the custom regex.
84
164
 
85
165
  Example:
86
166
  from django.urls import path, register_converter
87
- from django_display_ids.converters import DisplayIDOrUUIDConverter
167
+ from django_display_ids.converters import (
168
+ make_display_id_or_uuid_or_slug_converter,
169
+ )
88
170
 
89
- register_converter(DisplayIDOrUUIDConverter, "display_id_or_uuid")
171
+ # Lowercase slugs only
172
+ Converter = make_display_id_or_uuid_or_slug_converter(r"[a-z0-9-]+")
173
+ register_converter(Converter, "identifier")
90
174
 
91
175
  urlpatterns = [
92
- path("invoices/<display_id_or_uuid:id>/", InvoiceDetailView.as_view()),
176
+ path("products/<identifier:id>/", ProductDetailView.as_view()),
93
177
  ]
94
178
  """
179
+ from .conf import get_setting
95
180
 
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
- )
181
+ pattern = slug_regex if slug_regex is not None else get_setting("SLUG_REGEX")
104
182
 
105
- def to_python(self, value: str) -> str:
106
- """Convert the URL value to a Python object."""
107
- return value
183
+ class CustomDisplayIDOrUUIDOrSlugConverter(DisplayIDOrUUIDOrSlugConverter):
184
+ regex = rf"(?:{DISPLAY_ID_REGEX}|{UUID_REGEX}|{pattern})"
108
185
 
109
- def to_url(self, value: str) -> str:
110
- """Convert a Python object to a URL string."""
111
- return value
186
+ return CustomDisplayIDOrUUIDOrSlugConverter
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-display-ids
3
- Version: 0.3.1
3
+ Version: 0.3.2
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
@@ -123,8 +123,8 @@ See the [contributing guide](https://django-display-ids.readthedocs.io/en/latest
123
123
 
124
124
  If you need ID generation and storage (custom model fields), consider:
125
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
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
129
 
130
130
  **django-display-ids** works with existing UUID fields and handles resolution only — no migrations required.
@@ -1,13 +1,13 @@
1
- django_display_ids/__init__.py,sha256=2qX0YC7Jwpoom5rLOhvOewR3GQEIbUhu5ZYwjfEoklo,3207
1
+ django_display_ids/__init__.py,sha256=wwFvtGjdQia56uY893Jz5iSwaqyH4KXbgvd3nxdzGss,3496
2
2
  django_display_ids/admin.py,sha256=_voqWbr8AwPRC_uCTJWTcEhAhc7RZUgvs7DyVsutDuw,3046
3
3
  django_display_ids/apps.py,sha256=UqblGiYNONOIEH-giEAuKp1YDgxl2yf0jS0ELMj1iig,315
4
- django_display_ids/conf.py,sha256=Mg5ZIhTRSF5dzNO_iYndoF4_g5Umo-5oUw1vB76M7fw,1230
4
+ django_display_ids/conf.py,sha256=6bHdUwDkuroYEPmOlPiAc49EcxlOG_QO0aHHawckOe8,1404
5
5
  django_display_ids/contrib/__init__.py,sha256=sxGJK8Whb6cL7RqACqGRYrIZvaMwP3l6dYk3mIYWzDY,62
6
6
  django_display_ids/contrib/drf_spectacular/__init__.py,sha256=21n56CH7tp2lKq4EGW99KVEgVB9fJ5GnfllUW_KrIe8,4003
7
7
  django_display_ids/contrib/rest_framework/__init__.py,sha256=Xun6zMhCZzQJZQ7ywvBYoKhTJIZEV84AntrbSWfBjYI,1567
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=nDaze7MJwVqL0MrxQLOur3sPVrRXud_WT4ijGR02jDY,6087
10
- django_display_ids/converters.py,sha256=9xP3vKW1keN1glJvwmZun0BaBRLMKio0aD7Coc888ms,3197
10
+ django_display_ids/converters.py,sha256=ElwrfA7DXiadSZ-Sjvl6ZALgH7tfEZ-tLI7UdE6MsAs,5797
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=nmyRfpsqVvz226Zcu_QANwr8MudbfoX09mAgOCwuPuQ,3022
@@ -20,6 +20,6 @@ django_display_ids/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
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=-y_Zwo4QLU0lPRPjABpijsze5vsG0CBvJtrVwVtuLwM,5127
23
- django_display_ids-0.3.1.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
24
- django_display_ids-0.3.1.dist-info/METADATA,sha256=eKOjG80Rb_fK-_FQtUYR67fyWGZlN5SzgZuhjD4RGDA,5295
25
- django_display_ids-0.3.1.dist-info/RECORD,,
23
+ django_display_ids-0.3.2.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
24
+ django_display_ids-0.3.2.dist-info/METADATA,sha256=qlsCntznDOhR1wMFyPhU2AAnWyodabq1nMHtQm2HczA,5283
25
+ django_display_ids-0.3.2.dist-info/RECORD,,