django-display-ids 0.3.2__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.
@@ -9,7 +9,7 @@ Example:
9
9
  encode_display_id,
10
10
  decode_display_id,
11
11
  resolve_object,
12
- DisplayIDObjectMixin,
12
+ DisplayIDMixin,
13
13
  )
14
14
 
15
15
  # Encode a UUID to a display ID
@@ -17,15 +17,18 @@ Example:
17
17
  # -> "inv_2aUyqjCzEIiEcYMKj7TZtw"
18
18
 
19
19
  # Use in Django views
20
- class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
20
+ class InvoiceDetailView(DisplayIDMixin, DetailView):
21
21
  model = Invoice
22
22
  lookup_param = "id"
23
23
  display_id_prefix = "inv"
24
24
  """
25
25
 
26
+ from importlib.metadata import version
26
27
  from typing import Any
27
28
 
28
- from .admin import DisplayIDSearchMixin
29
+ from .admin import DisplayIDAdminSearchMixin
30
+
31
+ __version__ = version("django-display-ids")
29
32
  from .converters import (
30
33
  DisplayIDConverter,
31
34
  DisplayIDOrSlugConverter,
@@ -48,8 +51,8 @@ from .examples import (
48
51
  )
49
52
  from .exceptions import (
50
53
  AmbiguousIdentifierError,
54
+ DisplayIDLookupError,
51
55
  InvalidIdentifierError,
52
- LookupError,
53
56
  MissingPrefixError,
54
57
  ObjectNotFoundError,
55
58
  UnknownPrefixError,
@@ -57,15 +60,15 @@ from .exceptions import (
57
60
  from .managers import DisplayIDManager, DisplayIDQuerySet
58
61
  from .resolver import resolve_object
59
62
  from .typing import DEFAULT_STRATEGIES, StrategyName
60
- from .views import DisplayIDObjectMixin
63
+ from .views import DisplayIDMixin
61
64
 
62
65
 
63
66
  def __getattr__(name: str) -> Any:
64
67
  """Lazy import for model-related items to avoid app registry issues."""
65
- if name == "DisplayIDMixin":
66
- from .models import DisplayIDMixin
68
+ if name == "DisplayIDModel":
69
+ from .models import DisplayIDModel
67
70
 
68
- return DisplayIDMixin
71
+ return DisplayIDModel
69
72
  if name == "get_model_for_prefix":
70
73
  from .models import get_model_for_prefix
71
74
 
@@ -74,57 +77,43 @@ def __getattr__(name: str) -> Any:
74
77
 
75
78
 
76
79
  __all__ = [ # noqa: RUF022 - keep categorized order for readability
80
+ # Model integration
81
+ "DisplayIDModel",
82
+ "DisplayIDManager",
83
+ "DisplayIDQuerySet",
84
+ "get_model_for_prefix",
85
+ # View mixins
86
+ "DisplayIDMixin",
87
+ "DisplayIDAdminSearchMixin",
88
+ # Encoding/decoding
89
+ "encode_display_id",
90
+ "decode_display_id",
91
+ "encode_uuid",
92
+ "decode_uuid",
77
93
  # URL converters
78
94
  "DisplayIDConverter",
79
- "DisplayIDOrSlugConverter",
80
95
  "DisplayIDOrUUIDConverter",
96
+ "DisplayIDOrSlugConverter",
81
97
  "DisplayIDOrUUIDOrSlugConverter",
82
98
  "make_display_id_or_slug_converter",
83
99
  "make_display_id_or_uuid_or_slug_converter",
84
- # Encoding
85
- "encode_uuid",
86
- "decode_uuid",
87
- "encode_display_id",
88
- "decode_display_id",
89
- # Examples (for OpenAPI schemas, documentation)
90
- "example_uuid",
91
- "example_display_id",
92
- "example_uuid_for_prefix", # alias
93
- "example_display_id_for_prefix", # alias
94
100
  # Core resolver
95
101
  "resolve_object",
96
- # Errors
97
- "LookupError",
102
+ # Exceptions
103
+ "DisplayIDLookupError",
98
104
  "InvalidIdentifierError",
99
105
  "UnknownPrefixError",
100
106
  "MissingPrefixError",
101
107
  "ObjectNotFoundError",
102
108
  "AmbiguousIdentifierError",
103
- # Django mixins
104
- "DisplayIDObjectMixin",
105
- # Admin mixins
106
- "DisplayIDSearchMixin",
107
- # Model mixin
108
- "DisplayIDMixin",
109
- "get_model_for_prefix",
110
- # Managers
111
- "DisplayIDManager",
112
- "DisplayIDQuerySet",
109
+ # Examples (for OpenAPI)
110
+ "example_display_id",
111
+ "example_uuid",
112
+ "example_display_id_for_prefix", # alias
113
+ "example_uuid_for_prefix", # alias
113
114
  # Types
114
115
  "StrategyName",
115
116
  "DEFAULT_STRATEGIES",
117
+ # Version
118
+ "__version__",
116
119
  ]
117
-
118
-
119
- def get_drf_mixin() -> type:
120
- """Lazily import the DRF mixin to avoid hard dependency.
121
-
122
- Returns:
123
- DisplayIDLookupMixin class.
124
-
125
- Raises:
126
- ImportError: If Django REST Framework is not installed.
127
- """
128
- from .contrib.rest_framework import DisplayIDLookupMixin
129
-
130
- return DisplayIDLookupMixin
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import contextlib
6
- import uuid
7
6
  from typing import TYPE_CHECKING, Any
8
7
 
9
8
  from .encoding import decode_display_id
@@ -12,30 +11,33 @@ if TYPE_CHECKING:
12
11
  from django.db.models import Model, QuerySet
13
12
  from django.http import HttpRequest
14
13
 
15
- __all__ = ["DisplayIDSearchMixin"]
14
+ __all__ = ["DisplayIDAdminSearchMixin"]
16
15
 
17
16
 
18
- class DisplayIDSearchMixin:
19
- """Mixin to enable searching by display_id or UUID in Django admin.
17
+ class DisplayIDAdminSearchMixin:
18
+ """Mixin to enable searching by display ID in Django admin.
20
19
 
21
20
  Add this mixin to your ModelAdmin to allow searching by display ID
22
- (e.g., "inv_2aUyqjCzEIiEcYMKj7TZtw") or raw UUID
23
- (e.g., "550e8400-e29b-41d4-a716-446655440000") in the admin search box.
21
+ (e.g., "inv_2aUyqjCzEIiEcYMKj7TZtw") in the admin search box.
24
22
 
25
- The mixin decodes the identifier and searches by the UUID field.
23
+ The mixin decodes the display ID and searches by the UUID field.
24
+
25
+ For raw UUID search, add the UUID field to ``search_fields`` instead::
26
+
27
+ search_fields = ["name", "id"] # "id" enables raw UUID search
26
28
 
27
29
  Example:
28
30
  from django.contrib import admin
29
- from django_display_ids.admin import DisplayIDSearchMixin
31
+ from django_display_ids import DisplayIDAdminSearchMixin
30
32
 
31
33
  @admin.register(Invoice)
32
- class InvoiceAdmin(DisplayIDSearchMixin, admin.ModelAdmin):
34
+ class InvoiceAdmin(DisplayIDAdminSearchMixin, admin.ModelAdmin):
33
35
  list_display = ["id", "display_id", "name"]
34
- search_fields = ["name"] # display_id/UUID search is automatic
36
+ search_fields = ["name"] # display ID search is automatic
35
37
 
36
38
  Attributes:
37
39
  uuid_field: Name of the UUID field to search. Defaults to model's
38
- uuid_field if using DisplayIDMixin, otherwise "id".
40
+ uuid_field if using DisplayIDModel, otherwise "id".
39
41
  """
40
42
 
41
43
  uuid_field: str | None = None
@@ -49,43 +51,26 @@ class DisplayIDSearchMixin:
49
51
  uuid_field: str | None = getattr(self.model, "uuid_field", None)
50
52
  return uuid_field or "id"
51
53
 
52
- def _try_parse_uuid(self, value: str) -> uuid.UUID | None:
53
- """Try to parse a string as a UUID."""
54
- try:
55
- return uuid.UUID(value)
56
- except (ValueError, TypeError):
57
- return None
58
-
59
54
  def get_search_results(
60
55
  self,
61
56
  request: HttpRequest,
62
57
  queryset: QuerySet[Any],
63
58
  search_term: str,
64
59
  ) -> tuple[QuerySet[Any], bool]:
65
- """Extend search to handle display IDs and raw UUIDs.
60
+ """Extend search to handle display IDs.
66
61
 
67
- Tries to match the search term as:
68
- 1. A display ID (prefix_base62uuid) if it contains an underscore
69
- 2. A raw UUID if it looks like a UUID format
62
+ Tries to decode the search term as a display ID (prefix_base62uuid)
63
+ if it contains an underscore.
70
64
  """
71
65
  queryset, use_distinct = super().get_search_results( # type: ignore[misc]
72
66
  request, queryset, search_term
73
67
  )
74
68
 
75
- uuid_field = self._get_uuid_field()
76
- uuid_val = None
77
-
78
69
  # Try to decode as display_id if it contains an underscore
79
70
  if "_" in search_term:
71
+ uuid_field = self._get_uuid_field()
80
72
  with contextlib.suppress(ValueError, TypeError):
81
73
  _prefix, uuid_val = decode_display_id(search_term)
82
-
83
- # Try to parse as raw UUID if not already matched
84
- if uuid_val is None:
85
- uuid_val = self._try_parse_uuid(search_term)
86
-
87
- # Add matching objects to queryset if we found a UUID
88
- if uuid_val is not None:
89
- queryset |= self.model._default_manager.filter(**{uuid_field: uuid_val})
74
+ queryset |= self.model._default_manager.filter(**{uuid_field: uuid_val})
90
75
 
91
76
  return queryset, use_distinct
@@ -11,7 +11,7 @@ Settings can be configured in Django settings under the DISPLAY_IDS namespace:
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- from typing import TYPE_CHECKING
14
+ from typing import TYPE_CHECKING, Any
15
15
 
16
16
  from django.conf import settings
17
17
  from django.urls.converters import SlugConverter
@@ -21,10 +21,16 @@ if TYPE_CHECKING:
21
21
 
22
22
  __all__ = [
23
23
  "DEFAULTS",
24
+ "NOT_SET",
24
25
  "SLUG_REGEX",
25
26
  "get_setting",
27
+ "get_slug_field",
28
+ "get_uuid_field",
26
29
  ]
27
30
 
31
+ # Sentinel for distinguishing "not set" from None
32
+ NOT_SET: Any = object()
33
+
28
34
  # Django's default slug regex pattern
29
35
  SLUG_REGEX: str = SlugConverter.regex
30
36
 
@@ -56,3 +62,31 @@ def get_setting(name: str) -> str | tuple[StrategyName, ...]:
56
62
  )
57
63
  result = user_settings.get(name, DEFAULTS[name])
58
64
  return result # type: ignore[return-value]
65
+
66
+
67
+ def get_uuid_field(override: str | None) -> str:
68
+ """Get the UUID field name, with optional override.
69
+
70
+ Args:
71
+ override: Explicit field name, or None to use settings default.
72
+
73
+ Returns:
74
+ The UUID field name.
75
+ """
76
+ if override is not None:
77
+ return override
78
+ return str(get_setting("UUID_FIELD"))
79
+
80
+
81
+ def get_slug_field(override: str | None) -> str:
82
+ """Get the slug field name, with optional override.
83
+
84
+ Args:
85
+ override: Explicit field name, or None to use settings default.
86
+
87
+ Returns:
88
+ The slug field name.
89
+ """
90
+ if override is not None:
91
+ return override
92
+ return str(get_setting("SLUG_FIELD"))
@@ -1,13 +1,62 @@
1
- """drf-spectacular extension for DisplayIDField.
1
+ """drf-spectacular integration for django-display-ids.
2
2
 
3
- This extension auto-registers when drf-spectacular is installed, providing
4
- proper OpenAPI schema generation for DisplayIDField.
3
+ This module provides:
4
+ - OpenAPI schema extension for DisplayIDField (auto-registers when imported)
5
+ - Helper functions for documenting URL path parameters
5
6
  """
6
7
 
7
8
  from __future__ import annotations
8
9
 
9
10
  from typing import TYPE_CHECKING, Any
10
11
 
12
+ # OpenAPI parameter description helpers
13
+ # These work regardless of whether drf-spectacular is installed
14
+
15
+
16
+ def id_param_description(
17
+ prefix: str, *, with_uuid: bool = True, with_slug: bool = False
18
+ ) -> str:
19
+ """Generate ID parameter description with the actual prefix.
20
+
21
+ Args:
22
+ prefix: The display_id prefix (e.g., "user", "app").
23
+ with_uuid: Include UUID as an identifier option.
24
+ with_slug: Include slug as an identifier option.
25
+
26
+ Returns:
27
+ Description string for OpenAPI parameter.
28
+
29
+ Example:
30
+ >>> id_param_description("user")
31
+ 'Identifier: display_id (user_xxx) or UUID'
32
+
33
+ >>> id_param_description("user", with_uuid=False)
34
+ 'Identifier: display_id (user_xxx)'
35
+
36
+ >>> id_param_description("app", with_slug=True)
37
+ 'Identifier: display_id (app_xxx), UUID, or slug'
38
+
39
+ >>> id_param_description("app", with_uuid=False, with_slug=True)
40
+ 'Identifier: display_id (app_xxx) or slug'
41
+ """
42
+ parts = [f"display_id ({prefix}_xxx)"]
43
+ if with_uuid:
44
+ parts.append("UUID")
45
+ if with_slug:
46
+ parts.append("slug")
47
+
48
+ if len(parts) == 1:
49
+ return f"Identifier: {parts[0]}"
50
+ elif len(parts) == 2:
51
+ return f"Identifier: {parts[0]} or {parts[1]}"
52
+ else:
53
+ return f"Identifier: {', '.join(parts[:-1])}, or {parts[-1]}"
54
+
55
+
56
+ __all__ = [
57
+ "id_param_description",
58
+ ]
59
+
11
60
  try:
12
61
  from drf_spectacular.extensions import OpenApiSerializerFieldExtension
13
62
  except ImportError:
@@ -1,48 +1,9 @@
1
1
  """Django REST Framework integration for django-display-ids."""
2
2
 
3
- import contextlib
4
-
5
3
  from .serializers import DisplayIDField
6
- from .views import DisplayIDLookupMixin
7
-
8
- # Register drf-spectacular extension if available
9
- # The extension auto-registers when the module is imported
10
- with contextlib.suppress(ImportError):
11
- from django_display_ids.contrib import (
12
- drf_spectacular as _drf_spectacular, # noqa: F401
13
- )
14
-
15
- # OpenAPI parameter descriptions for consistent documentation
16
- ID_PARAM_DESCRIPTION = "Identifier: display_id (prefix_xxx) or UUID"
17
- ID_PARAM_DESCRIPTION_WITH_SLUG = "Identifier: display_id (prefix_xxx), UUID, or slug"
18
-
19
-
20
- def id_param_description(prefix: str, *, with_slug: bool = False) -> str:
21
- """Generate ID parameter description with the actual prefix.
22
-
23
- Args:
24
- prefix: The display_id prefix (e.g., "user", "app").
25
- with_slug: Include slug as an identifier option.
26
-
27
- Returns:
28
- Description string for OpenAPI parameter.
29
-
30
- Example:
31
- >>> id_param_description("user")
32
- 'Identifier: display_id (user_xxx) or UUID'
33
-
34
- >>> id_param_description("app", with_slug=True)
35
- 'Identifier: display_id (app_xxx), UUID, or slug'
36
- """
37
- if with_slug:
38
- return f"Identifier: display_id ({prefix}_xxx), UUID, or slug"
39
- return f"Identifier: display_id ({prefix}_xxx) or UUID"
40
-
4
+ from .views import DisplayIDMixin
41
5
 
42
- __all__ = [ # noqa: RUF022 - keep logical order for readability
6
+ __all__ = [
43
7
  "DisplayIDField",
44
- "DisplayIDLookupMixin",
45
- "ID_PARAM_DESCRIPTION",
46
- "ID_PARAM_DESCRIPTION_WITH_SLUG",
47
- "id_param_description",
8
+ "DisplayIDMixin",
48
9
  ]
@@ -4,11 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from django_display_ids.conf import get_setting
7
+ from django_display_ids.conf import (
8
+ NOT_SET,
9
+ get_setting,
10
+ get_slug_field,
11
+ get_uuid_field,
12
+ )
8
13
  from django_display_ids.encoding import PREFIX_PATTERN
9
14
  from django_display_ids.exceptions import (
15
+ DisplayIDLookupError,
10
16
  InvalidIdentifierError,
11
- LookupError,
12
17
  ObjectNotFoundError,
13
18
  UnknownPrefixError,
14
19
  )
@@ -19,11 +24,9 @@ if TYPE_CHECKING:
19
24
  from django.db import models
20
25
 
21
26
  __all__ = [
22
- "DisplayIDLookupMixin",
27
+ "DisplayIDMixin",
23
28
  ]
24
29
 
25
- _NOT_SET: Any = object()
26
-
27
30
 
28
31
  def _get_drf_exceptions() -> tuple[type[Exception], type[Exception]]:
29
32
  """Lazily import DRF exceptions to avoid hard dependency."""
@@ -33,12 +36,12 @@ def _get_drf_exceptions() -> tuple[type[Exception], type[Exception]]:
33
36
  return NotFound, ParseError
34
37
  except ImportError:
35
38
  raise ImportError(
36
- "Django REST Framework is required for DisplayIDLookupMixin. "
39
+ "Django REST Framework is required for DisplayIDMixin. "
37
40
  "Install it with: pip install djangorestframework"
38
41
  ) from None
39
42
 
40
43
 
41
- class DisplayIDLookupMixin:
44
+ class DisplayIDMixin:
42
45
  """Mixin for DRF views that resolves objects by display ID, UUID, or slug.
43
46
 
44
47
  Works with APIView, GenericAPIView, and ViewSets. Does not require
@@ -52,7 +55,7 @@ class DisplayIDLookupMixin:
52
55
  slug_field: Name of the slug field on the model.
53
56
 
54
57
  Example:
55
- class InvoiceView(DisplayIDLookupMixin, APIView):
58
+ class InvoiceView(DisplayIDMixin, APIView):
56
59
  lookup_url_kwarg = "id"
57
60
  lookup_strategies = ("display_id", "uuid")
58
61
  display_id_prefix = "inv"
@@ -62,7 +65,7 @@ class DisplayIDLookupMixin:
62
65
  return Response({"id": str(invoice.id)})
63
66
 
64
67
  Example with ViewSet:
65
- class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
68
+ class InvoiceViewSet(DisplayIDMixin, ModelViewSet):
66
69
  queryset = Invoice.objects.all()
67
70
  serializer_class = InvoiceSerializer
68
71
  lookup_url_kwarg = "pk"
@@ -71,7 +74,7 @@ class DisplayIDLookupMixin:
71
74
 
72
75
  lookup_url_kwarg: str = "pk"
73
76
  lookup_strategies: tuple[StrategyName, ...] | None = None
74
- display_id_prefix: str | None = _NOT_SET
77
+ display_id_prefix: str | None = NOT_SET
75
78
  uuid_field: str | None = None
76
79
  slug_field: str | None = None
77
80
 
@@ -80,14 +83,10 @@ class DisplayIDLookupMixin:
80
83
  request: Any
81
84
 
82
85
  def _get_uuid_field(self) -> str:
83
- if self.uuid_field is not None:
84
- return self.uuid_field
85
- return str(get_setting("UUID_FIELD"))
86
+ return get_uuid_field(self.uuid_field)
86
87
 
87
88
  def _get_slug_field(self) -> str:
88
- if self.slug_field is not None:
89
- return self.slug_field
90
- return str(get_setting("SLUG_FIELD"))
89
+ return get_slug_field(self.slug_field)
91
90
 
92
91
  def _get_strategies(self) -> tuple[StrategyName, ...]:
93
92
  if self.lookup_strategies is not None:
@@ -101,7 +100,7 @@ class DisplayIDLookupMixin:
101
100
  explicitly disable), otherwise falls back to the model's
102
101
  display_id_prefix attribute.
103
102
  """
104
- if self.display_id_prefix is not _NOT_SET:
103
+ if self.display_id_prefix is not NOT_SET:
105
104
  if self.display_id_prefix is not None and not PREFIX_PATTERN.match(
106
105
  self.display_id_prefix
107
106
  ):
@@ -168,7 +167,7 @@ class DisplayIDLookupMixin:
168
167
  raise NotFound(str(e)) from e
169
168
  except (InvalidIdentifierError, UnknownPrefixError) as e:
170
169
  raise ParseError(str(e)) from e
171
- except LookupError as e:
170
+ except DisplayIDLookupError as e:
172
171
  raise ParseError(str(e)) from e
173
172
 
174
173
  # Check object-level permissions
@@ -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):
@@ -4,21 +4,21 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = [
6
6
  "AmbiguousIdentifierError",
7
+ "DisplayIDLookupError",
7
8
  "InvalidIdentifierError",
8
- "LookupError",
9
9
  "MissingPrefixError",
10
10
  "ObjectNotFoundError",
11
11
  "UnknownPrefixError",
12
12
  ]
13
13
 
14
14
 
15
- class LookupError(Exception):
15
+ class DisplayIDLookupError(Exception):
16
16
  """Base exception for all lookup errors."""
17
17
 
18
18
  pass
19
19
 
20
20
 
21
- class InvalidIdentifierError(LookupError):
21
+ class InvalidIdentifierError(DisplayIDLookupError):
22
22
  """Raised when an identifier has an invalid format.
23
23
 
24
24
  This indicates the identifier string cannot be parsed as any
@@ -31,7 +31,7 @@ class InvalidIdentifierError(LookupError):
31
31
  super().__init__(self.message)
32
32
 
33
33
 
34
- class UnknownPrefixError(LookupError):
34
+ class UnknownPrefixError(DisplayIDLookupError):
35
35
  """Raised when a display ID has an unexpected prefix.
36
36
 
37
37
  This occurs when prefix enforcement is enabled and the
@@ -49,7 +49,7 @@ class UnknownPrefixError(LookupError):
49
49
  super().__init__(message)
50
50
 
51
51
 
52
- class MissingPrefixError(LookupError):
52
+ class MissingPrefixError(DisplayIDLookupError):
53
53
  """Raised when a display ID lookup is attempted without a prefix.
54
54
 
55
55
  This occurs when calling get_by_display_id() on a model that
@@ -68,7 +68,7 @@ class MissingPrefixError(LookupError):
68
68
  super().__init__(message)
69
69
 
70
70
 
71
- class ObjectNotFoundError(LookupError):
71
+ class ObjectNotFoundError(DisplayIDLookupError):
72
72
  """Raised when no object matches the identifier.
73
73
 
74
74
  This indicates the identifier was valid but no matching
@@ -85,7 +85,7 @@ class ObjectNotFoundError(LookupError):
85
85
  super().__init__(message)
86
86
 
87
87
 
88
- class AmbiguousIdentifierError(LookupError):
88
+ class AmbiguousIdentifierError(DisplayIDLookupError):
89
89
  """Raised when an identifier matches multiple objects.
90
90
 
91
91
  This can occur with slug lookups if slugs are not unique,
@@ -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,
@@ -10,7 +10,7 @@ from .conf import get_setting
10
10
  from .encoding import PREFIX_PATTERN, encode_display_id
11
11
 
12
12
  __all__ = [
13
- "DisplayIDMixin",
13
+ "DisplayIDModel",
14
14
  "get_model_for_prefix",
15
15
  ]
16
16
 
@@ -50,14 +50,14 @@ def _register_prefix(prefix: str, model_name: str) -> None:
50
50
  _prefix_registry[prefix] = model_name
51
51
 
52
52
 
53
- class DisplayIDMixin(models.Model):
54
- """Mixin that adds display_id support to a Django model.
53
+ class DisplayIDModel(models.Model):
54
+ """Abstract base model that adds display_id support.
55
55
 
56
56
  Subclasses must define `display_id_prefix` as a class attribute.
57
57
  Optionally override `uuid_field` or `slug_field` if using non-default field names.
58
58
 
59
59
  Example:
60
- class Invoice(DisplayIDMixin):
60
+ class Invoice(DisplayIDModel):
61
61
  display_id_prefix = "inv"
62
62
 
63
63
  id = models.UUIDField(primary_key=True, default=uuid.uuid4)
@@ -67,7 +67,7 @@ class DisplayIDMixin(models.Model):
67
67
  invoice.display_id # -> "inv_2aUyqjCzEIiEcYMKj7TZtw"
68
68
 
69
69
  Example with custom field names:
70
- class Product(DisplayIDMixin):
70
+ class Product(DisplayIDModel):
71
71
  display_id_prefix = "prod"
72
72
  uuid_field = "uid"
73
73
  slug_field = "handle"
@@ -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"):
@@ -6,11 +6,11 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from django.http import Http404
8
8
 
9
- from .conf import get_setting
9
+ from .conf import NOT_SET, get_setting, get_slug_field, get_uuid_field
10
10
  from .encoding import PREFIX_PATTERN
11
11
  from .exceptions import (
12
+ DisplayIDLookupError,
12
13
  InvalidIdentifierError,
13
- LookupError,
14
14
  ObjectNotFoundError,
15
15
  UnknownPrefixError,
16
16
  )
@@ -21,13 +21,11 @@ if TYPE_CHECKING:
21
21
  from django.db import models
22
22
 
23
23
  __all__ = [
24
- "DisplayIDObjectMixin",
24
+ "DisplayIDMixin",
25
25
  ]
26
26
 
27
- _NOT_SET: Any = object()
28
27
 
29
-
30
- class DisplayIDObjectMixin:
28
+ class DisplayIDMixin:
31
29
  """Mixin for Django CBVs that resolves objects by display ID, UUID, or slug.
32
30
 
33
31
  Drop-in replacement for SingleObjectMixin's get_object() method.
@@ -42,7 +40,7 @@ class DisplayIDObjectMixin:
42
40
  slug_field: Name of the slug field on the model.
43
41
 
44
42
  Example:
45
- class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
43
+ class InvoiceDetailView(DisplayIDMixin, DetailView):
46
44
  model = Invoice
47
45
  lookup_param = "id"
48
46
  lookup_strategies = ("display_id", "uuid")
@@ -52,19 +50,15 @@ class DisplayIDObjectMixin:
52
50
  model: type[models.Model] | None = None
53
51
  lookup_param: str = "pk"
54
52
  lookup_strategies: tuple[StrategyName, ...] | None = None
55
- display_id_prefix: str | None = _NOT_SET
53
+ display_id_prefix: str | None = NOT_SET
56
54
  uuid_field: str | None = None
57
55
  slug_field: str | None = None
58
56
 
59
57
  def _get_uuid_field(self) -> str:
60
- if self.uuid_field is not None:
61
- return self.uuid_field
62
- return str(get_setting("UUID_FIELD"))
58
+ return get_uuid_field(self.uuid_field)
63
59
 
64
60
  def _get_slug_field(self) -> str:
65
- if self.slug_field is not None:
66
- return self.slug_field
67
- return str(get_setting("SLUG_FIELD"))
61
+ return get_slug_field(self.slug_field)
68
62
 
69
63
  def _get_strategies(self) -> tuple[StrategyName, ...]:
70
64
  if self.lookup_strategies is not None:
@@ -78,7 +72,7 @@ class DisplayIDObjectMixin:
78
72
  explicitly disable), otherwise falls back to the model's
79
73
  display_id_prefix attribute.
80
74
  """
81
- if self.display_id_prefix is not _NOT_SET:
75
+ if self.display_id_prefix is not NOT_SET:
82
76
  if self.display_id_prefix is not None and not PREFIX_PATTERN.match(
83
77
  self.display_id_prefix
84
78
  ):
@@ -146,5 +140,5 @@ class DisplayIDObjectMixin:
146
140
  raise Http404(str(e)) from e
147
141
  except (InvalidIdentifierError, UnknownPrefixError) as e:
148
142
  raise Http404(str(e)) from e
149
- except LookupError as e:
143
+ except DisplayIDLookupError as e:
150
144
  raise Http404(str(e)) from e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-display-ids
3
- Version: 0.3.2
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
@@ -66,9 +67,9 @@ INSTALLED_APPS = [
66
67
 
67
68
  ```python
68
69
  from django.views.generic import DetailView
69
- from django_display_ids import DisplayIDObjectMixin
70
+ from django_display_ids import DisplayIDMixin
70
71
 
71
- class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
72
+ class InvoiceDetailView(DisplayIDMixin, DetailView):
72
73
  model = Invoice
73
74
  lookup_param = "id"
74
75
  lookup_strategies = ("display_id", "uuid", "slug")
@@ -79,9 +80,9 @@ class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
79
80
 
80
81
  ```python
81
82
  from rest_framework.viewsets import ModelViewSet
82
- from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin
83
+ from django_display_ids.contrib.rest_framework import DisplayIDMixin
83
84
 
84
- class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
85
+ class InvoiceViewSet(DisplayIDMixin, ModelViewSet):
85
86
  queryset = Invoice.objects.all()
86
87
  serializer_class = InvoiceSerializer
87
88
  lookup_url_kwarg = "id"
@@ -0,0 +1,25 @@
1
+ django_display_ids/__init__.py,sha256=zFd0XNNOeVgpIImWBe_b-HD59svbVwkDVYq8X6lsLug,3256
2
+ django_display_ids/admin.py,sha256=UPmU-kGsZ5x_-r9n99P17La5lr-3BLvB9qTfWJgklt8,2569
3
+ django_display_ids/apps.py,sha256=UqblGiYNONOIEH-giEAuKp1YDgxl2yf0jS0ELMj1iig,315
4
+ django_display_ids/conf.py,sha256=RXcWGyRfLS9J5fyUaoM5ASq2rwOFSxX22tvSX-TB2ig,2215
5
+ django_display_ids/contrib/__init__.py,sha256=sxGJK8Whb6cL7RqACqGRYrIZvaMwP3l6dYk3mIYWzDY,62
6
+ django_display_ids/contrib/drf_spectacular/__init__.py,sha256=9swSJge_dQldC4AZMkYI3M04LzU20eJ_2oz3XABviFA,5427
7
+ django_display_ids/contrib/rest_framework/__init__.py,sha256=pKV6BVB7k0dJm29C7XMr0M-wK2lXPUgW8Hf4BZHHwuE,198
8
+ django_display_ids/contrib/rest_framework/serializers.py,sha256=Jp-z7qHafxkGNYv30YC9rrqLt936SrhONJv3rqfQaC0,4049
9
+ django_display_ids/contrib/rest_framework/views.py,sha256=I6jpsdpM1sRKrPBVyrjypsKgFyUdu6WljGcffHXDiCw,5961
10
+ django_display_ids/converters.py,sha256=KGD5FYWkuXztO-6TvTtPwMP7xbnAXAaYuk9LvIpxkBM,5918
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=ATIW6SLM9zQ0s-26c3qdQfnPDbuiMpMZK6fxdyQN0tA,3085
14
+ django_display_ids/managers.py,sha256=I7qY1V64lDPplhE56ueARICFOHEQqCJhme6LQfBWo_o,11031
15
+ django_display_ids/models.py,sha256=_hmgAR4HC3I-5wU2DND6uNLjEllu1Y9eaXeBQ9dWMNI,4313
16
+ django_display_ids/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ django_display_ids/resolver.py,sha256=YhgfkBEVKFsAA9UB66cbyND46sWYWBHPqOJy-yQWstA,3229
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=uAXQJryWQNnTSHIjTLhisrYnNiaTo1pLzkUdvYyOLRs,4992
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
@@ -1,25 +0,0 @@
1
- django_display_ids/__init__.py,sha256=wwFvtGjdQia56uY893Jz5iSwaqyH4KXbgvd3nxdzGss,3496
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=6bHdUwDkuroYEPmOlPiAc49EcxlOG_QO0aHHawckOe8,1404
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=ElwrfA7DXiadSZ-Sjvl6ZALgH7tfEZ-tLI7UdE6MsAs,5797
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=XI73N4bvxy1Pr2oHeTaJP3uq3huyCX67CFZ2T8mefsA,4317
16
- django_display_ids/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- django_display_ids/resolver.py,sha256=ZlDVoxX0PmVf0MSwPyiNNwQVzdqJGDGE8fm2iyV7QjE,2848
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.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,,