djhtmx 1.3.1__py3-none-any.whl → 1.3.3__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.
- djhtmx/__init__.py +1 -1
- djhtmx/introspection.py +52 -3
- {djhtmx-1.3.1.dist-info → djhtmx-1.3.3.dist-info}/METADATA +18 -1
- {djhtmx-1.3.1.dist-info → djhtmx-1.3.3.dist-info}/RECORD +6 -6
- {djhtmx-1.3.1.dist-info → djhtmx-1.3.3.dist-info}/WHEEL +0 -0
- {djhtmx-1.3.1.dist-info → djhtmx-1.3.3.dist-info}/licenses/LICENSE +0 -0
djhtmx/__init__.py
CHANGED
djhtmx/introspection.py
CHANGED
|
@@ -78,12 +78,14 @@ class _LazyModelProxy(Generic[M]): # noqa
|
|
|
78
78
|
__pk: Any | None
|
|
79
79
|
__select_related: Sequence[str] | None
|
|
80
80
|
__prefetch_related: Sequence[str | Prefetch] | None
|
|
81
|
+
__allow_none: bool
|
|
81
82
|
|
|
82
83
|
def __init__(
|
|
83
84
|
self,
|
|
84
85
|
model: type[M],
|
|
85
86
|
value: Any,
|
|
86
87
|
model_annotation: ModelConfig | None = None,
|
|
88
|
+
allow_none: bool = False,
|
|
87
89
|
):
|
|
88
90
|
self.__model = model
|
|
89
91
|
if value is None or isinstance(value, model):
|
|
@@ -98,12 +100,44 @@ class _LazyModelProxy(Generic[M]): # noqa
|
|
|
98
100
|
else:
|
|
99
101
|
self.__select_related = None
|
|
100
102
|
self.__prefetch_related = None
|
|
103
|
+
self.__allow_none = allow_none
|
|
104
|
+
|
|
105
|
+
def __bool__(self) -> bool:
|
|
106
|
+
"""Check if the instance exists. Called when proxy is used in boolean context."""
|
|
107
|
+
if self.__instance is None:
|
|
108
|
+
self.__ensure_instance()
|
|
109
|
+
if self.__instance is None:
|
|
110
|
+
# Object doesn't exist
|
|
111
|
+
if not self.__allow_none:
|
|
112
|
+
# Required field - raise exception
|
|
113
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
114
|
+
|
|
115
|
+
raise ObjectDoesNotExist(
|
|
116
|
+
f"{self.__model.__name__} with pk={self.__pk} does not exist "
|
|
117
|
+
"(object may have been deleted)"
|
|
118
|
+
)
|
|
119
|
+
# Optional field - return False (proxy is falsy)
|
|
120
|
+
return False
|
|
121
|
+
return True
|
|
101
122
|
|
|
102
123
|
def __getattr__(self, name: str) -> Any:
|
|
103
124
|
if name == "pk":
|
|
104
125
|
return self.__pk
|
|
105
126
|
if self.__instance is None:
|
|
106
127
|
self.__ensure_instance()
|
|
128
|
+
if self.__instance is None:
|
|
129
|
+
# Object doesn't exist (was deleted or never existed)
|
|
130
|
+
if self.__allow_none:
|
|
131
|
+
# Optional field (Model | None) - return None gracefully
|
|
132
|
+
return None
|
|
133
|
+
else:
|
|
134
|
+
# Required field (Model) - raise explicit exception
|
|
135
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
136
|
+
|
|
137
|
+
raise ObjectDoesNotExist(
|
|
138
|
+
f"{self.__model.__name__} with pk={self.__pk} does not exist "
|
|
139
|
+
"(object may have been deleted)"
|
|
140
|
+
)
|
|
107
141
|
return getattr(self.__instance, name)
|
|
108
142
|
|
|
109
143
|
def __ensure_instance(self):
|
|
@@ -143,9 +177,13 @@ class _ModelBeforeValidator(Generic[M]): # noqa
|
|
|
143
177
|
return None
|
|
144
178
|
elif isinstance(value, _LazyModelProxy):
|
|
145
179
|
instance = value._LazyModelProxy__instance or value._LazyModelProxy__pk
|
|
146
|
-
return _LazyModelProxy(
|
|
180
|
+
return _LazyModelProxy(
|
|
181
|
+
self.model, instance, model_annotation=self.model_config, allow_none=self.allow_none
|
|
182
|
+
)
|
|
147
183
|
else:
|
|
148
|
-
return _LazyModelProxy(
|
|
184
|
+
return _LazyModelProxy(
|
|
185
|
+
self.model, value, model_annotation=self.model_config, allow_none=self.allow_none
|
|
186
|
+
)
|
|
149
187
|
|
|
150
188
|
def _get_instance(self, value):
|
|
151
189
|
if value is None or isinstance(value, self.model):
|
|
@@ -254,7 +292,16 @@ def annotate_model(annotation, *, model_config: ModelConfig | None = None):
|
|
|
254
292
|
# Process the base type (first arg) and keep other metadata
|
|
255
293
|
base_type = args[0]
|
|
256
294
|
metadata = args[1:]
|
|
257
|
-
|
|
295
|
+
|
|
296
|
+
# Extract ModelConfig from metadata if present
|
|
297
|
+
extracted_model_config = next(
|
|
298
|
+
(m for m in metadata if isinstance(m, ModelConfig)),
|
|
299
|
+
None,
|
|
300
|
+
)
|
|
301
|
+
# Use extracted config, falling back to passed parameter
|
|
302
|
+
config_to_use = extracted_model_config or model_config
|
|
303
|
+
|
|
304
|
+
processed_base = annotate_model(base_type, model_config=config_to_use)
|
|
258
305
|
|
|
259
306
|
# If processed_base is also Annotated, merge the metadata
|
|
260
307
|
if get_origin(processed_base) is Annotated:
|
|
@@ -293,6 +340,8 @@ def annotate_model(annotation, *, model_config: ModelConfig | None = None):
|
|
|
293
340
|
return type_[
|
|
294
341
|
*(annotate_model(p, model_config=model_annotation) for p in params)
|
|
295
342
|
] # type: ignore
|
|
343
|
+
# Other generic types (list, dict, defaultdict, etc.) - return as-is
|
|
344
|
+
return annotation
|
|
296
345
|
else:
|
|
297
346
|
return annotation
|
|
298
347
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: djhtmx
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: Interactive UI Components for Django using HTMX
|
|
5
5
|
Project-URL: Homepage, https://github.com/edelvalle/djhtmx
|
|
6
6
|
Project-URL: Documentation, https://github.com/edelvalle/djhtmx#readme
|
|
@@ -311,6 +311,23 @@ class TodoComponent(HtmxComponent):
|
|
|
311
311
|
]
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
+
**Handling Deleted Objects:**
|
|
315
|
+
|
|
316
|
+
Lazy models handle deleted database objects gracefully:
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
class MyComponent(HtmxComponent):
|
|
320
|
+
# Required: raises ObjectDoesNotExist when checking if object was deleted
|
|
321
|
+
item: Annotated[Item, ModelConfig(lazy=True)]
|
|
322
|
+
|
|
323
|
+
# Optional: becomes falsy and returns None when object was deleted
|
|
324
|
+
archived_item: Annotated[Item | None, ModelConfig(lazy=True)]
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- **Required lazy models**: Checking truthiness (`if component.item:`) raises `ObjectDoesNotExist` with a clear message
|
|
328
|
+
- **Optional lazy models**: Checking truthiness returns `False`, field accesses return `None`
|
|
329
|
+
- **Both**: Accessing `.pk` always works without triggering database queries
|
|
330
|
+
|
|
314
331
|
## Component nesting
|
|
315
332
|
|
|
316
333
|
Components can contain components inside to decompose the behavior in more granular and specialized parts, for this you don't have to do anything but to a component inside the template of other component....
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
djhtmx/__init__.py,sha256=
|
|
1
|
+
djhtmx/__init__.py,sha256=VL_7rY7fcdRsK0N7SZK5nteSMhURTdTtuExuNLQaZBs,84
|
|
2
2
|
djhtmx/apps.py,sha256=hAyjzmInEstxLY9k8Qn58LvNlezgQLx5_NqyVL1WwYs,323
|
|
3
3
|
djhtmx/command_queue.py,sha256=LSUkb2YMRt1lDyOg6WP7PoHsObynec0B55JyFtcshT0,5090
|
|
4
4
|
djhtmx/commands.py,sha256=UxXbARd4Teetjh_zjvAWgI2KNbvdETH-WrGf4qD9Xr8,1206
|
|
@@ -7,7 +7,7 @@ djhtmx/consumer.py,sha256=0Yh8urgMH3khA6_pWeY049w3jqHWZL_K9dErOhNctQA,2898
|
|
|
7
7
|
djhtmx/context.py,sha256=cWvz8Z0MC6x_G8sn5mvoH8Hu38qReY21_eNdThuba1A,214
|
|
8
8
|
djhtmx/exceptions.py,sha256=UtyE1N-52OmzwgRM9xFxjUuhHTMDvD7Oy3rNpgthLcs,47
|
|
9
9
|
djhtmx/global_events.py,sha256=bYb8WmQn_WsZ_Dadr0pGiGOPia01K-VanPpM97Lt324,342
|
|
10
|
-
djhtmx/introspection.py,sha256=
|
|
10
|
+
djhtmx/introspection.py,sha256=s1hJ5EO-DcLOZwBiZSUAcnBsCi1ewwPlER1cFPO1QNo,19907
|
|
11
11
|
djhtmx/json.py,sha256=7cjwWIJj7e0dk54INKYZJe6zKkIW7wlsNSlD05cbXfY,1374
|
|
12
12
|
djhtmx/middleware.py,sha256=JuMtv9ZnpungTvQ1qD2Lg6LiFPB3knQlA1ERgH4iGl0,1274
|
|
13
13
|
djhtmx/query.py,sha256=GKubKKZ1Z6krzLjG4GZpcxAyPy-pCMCtyfSVfXrpy90,6855
|
|
@@ -31,7 +31,7 @@ djhtmx/templates/htmx/headers.html,sha256=z7r9klwBDXDyjbHrzatZeHDvXB2DaZhgu55CFb
|
|
|
31
31
|
djhtmx/templates/htmx/lazy.html,sha256=LfAThtKmFj-lCUZ7JWF_sC1Y6XsIpEz8A3IgWASn-J8,52
|
|
32
32
|
djhtmx/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
djhtmx/templatetags/htmx.py,sha256=-qFqz4T9mCJocG9XIIey81cCYwk07XUd_DMpxNdmbsM,8397
|
|
34
|
-
djhtmx-1.3.
|
|
35
|
-
djhtmx-1.3.
|
|
36
|
-
djhtmx-1.3.
|
|
37
|
-
djhtmx-1.3.
|
|
34
|
+
djhtmx-1.3.3.dist-info/METADATA,sha256=5UCGB6iJel1PhQxBnImgvcGp-n6jgfhWUEmNtz68Bck,33745
|
|
35
|
+
djhtmx-1.3.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
djhtmx-1.3.3.dist-info/licenses/LICENSE,sha256=kCi_iSBUGsRZInQn96w7LXYzjiRjZ8FXl6vP--mFRPk,1085
|
|
37
|
+
djhtmx-1.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|