django-ninja-aio-crud 2.10.1__py3-none-any.whl → 2.11.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ninja-aio-crud
3
- Version: 2.10.1
3
+ Version: 2.11.1
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
6
  Requires-Python: >=3.10, <3.15
@@ -1,9 +1,9 @@
1
- ninja_aio/__init__.py,sha256=89U1Dj8vplVSbgWDcQsklzcAzW070HQlarC0u9_LOX4,120
1
+ ninja_aio/__init__.py,sha256=lbKW8_3c2S8qEgGVj2hcT_ajL3kqrk8xwcGdWo1uf4I,120
2
2
  ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
3
3
  ninja_aio/auth.py,sha256=4sWdFPjKiQgUL1d_CSGDblVjnY5ptP6LQha6XXdluJA,9157
4
4
  ninja_aio/exceptions.py,sha256=_3xFqfFCOfrrMhSA0xbMqgXy8R0UQjhXaExrFvaDAjY,3891
5
5
  ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
6
- ninja_aio/renders.py,sha256=89g46NWUT8nmDG-rG0nxUYbAQWhuXcYKrPh7e1r_Fc4,1735
6
+ ninja_aio/renders.py,sha256=CW0xDa05Xna-UvL0MZqZeDEgueEaUassV_nG7Rh1-cw,1824
7
7
  ninja_aio/types.py,sha256=E3yfXbNKkvLVcr8bvkHTSyIiCRZ4zumzJaXj1aiBi5U,735
8
8
  ninja_aio/decorators/__init__.py,sha256=cDDHD_9EI4CP7c5eL1m2mGNl9bR24i8FAkQsT3_RNGM,371
9
9
  ninja_aio/decorators/operations.py,sha256=L9yt2ku5oo4CpOLixCADmkcFjLGsWAn-cg-sDcjFhMA,343
@@ -16,14 +16,15 @@ ninja_aio/helpers/query.py,sha256=NMGkS_v-ZVYKNtf1XohEUzfwca52Eq5FTcQ5lehHjus,46
16
16
  ninja_aio/models/__init__.py,sha256=L3UQnQAlKoI3F7jinadL-Nn55hkPvnSRPYW0JtnbWFo,114
17
17
  ninja_aio/models/serializers.py,sha256=I7pUz_vl0FElaVsrGaogT_Lj9T4uaNG7UDMGf5VMwW4,38468
18
18
  ninja_aio/models/utils.py,sha256=dxzwqE25b0lFZ--30XxeIJB-y4xbIDtc624vGaa_wOg,32885
19
- ninja_aio/schemas/__init__.py,sha256=_a092xZezlLc9QWCPWrybeklByCs39jbvf33zwdnrys,603
20
- ninja_aio/schemas/api.py,sha256=InzZgIFU4Zxkgj9u_zZzAMYs_vdaPD5eu12gG7xAFoQ,1047
19
+ ninja_aio/schemas/__init__.py,sha256=dHILiYBKMb51lDcyQdiXRw_0nzqM7Lu81UX2hv7kEfo,837
20
+ ninja_aio/schemas/api.py,sha256=dGUpJXR1iAf93QNR4kYj1uqIkTjiMfXultCotY6GtaQ,361
21
+ ninja_aio/schemas/filters.py,sha256=VxzH2xSWok8cUSkyfeqtrGhRewtFVmNHQfHNvY8Aynw,2662
21
22
  ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
22
23
  ninja_aio/schemas/helpers.py,sha256=Vti5BfHWpxaJXj_ixZBJb34VRwhHODrlVjRlIuHh_ug,8428
23
24
  ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
24
- ninja_aio/views/api.py,sha256=LUJBvD7oxE3nq6DlMDOHYzNi2KBYQMSbb7SzfZdqcp0,22197
25
- ninja_aio/views/mixins.py,sha256=YKdJVCjya-VB8bs62DNQZ9Gj3qHpsEJ-2257RbI0K1A,14238
26
- django_ninja_aio_crud-2.10.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
27
- django_ninja_aio_crud-2.10.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
28
- django_ninja_aio_crud-2.10.1.dist-info/METADATA,sha256=G-t2gFVZicsgYER6sY8HKnSwcNUfBwgClgRqkMRVCUE,9964
29
- django_ninja_aio_crud-2.10.1.dist-info/RECORD,,
25
+ ninja_aio/views/api.py,sha256=AAqkj0xT8J3PmJvsbluZ33cfrmrXJHiV9ARe2BqnfQ8,22492
26
+ ninja_aio/views/mixins.py,sha256=Zl6J8gbVagwT85bzDuKyJTk3iFxxFgX0YgYkjiUxZGg,17040
27
+ django_ninja_aio_crud-2.11.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
28
+ django_ninja_aio_crud-2.11.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
29
+ django_ninja_aio_crud-2.11.1.dist-info/METADATA,sha256=7ki9usiKl0L81q7qzlOGkjQCeJrKkPn6KXTq7CNs8D4,9964
30
+ django_ninja_aio_crud-2.11.1.dist-info/RECORD,,
ninja_aio/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django Ninja AIO CRUD - Rest Framework"""
2
2
 
3
- __version__ = "2.10.1"
3
+ __version__ = "2.11.1"
4
4
 
5
5
  from .api import NinjaAIO
6
6
 
ninja_aio/renders.py CHANGED
@@ -3,7 +3,7 @@ from ipaddress import IPv4Address, IPv6Address
3
3
  from typing import Any
4
4
 
5
5
  import orjson
6
- from django.http import HttpRequest
6
+ from django.http import HttpRequest, HttpResponseBase
7
7
  from django.conf import settings
8
8
  from ninja.renderers import BaseRenderer
9
9
  from pydantic import AnyUrl
@@ -14,6 +14,8 @@ class ORJSONRenderer(BaseRenderer):
14
14
  option = getattr(settings, "NINJA_AIO_ORJSON_RENDERER_OPTION", None)
15
15
 
16
16
  def render(self, request: HttpRequest, data: dict, *, response_status):
17
+ if isinstance(data, HttpResponseBase):
18
+ return data
17
19
  try:
18
20
  old_d = data
19
21
  for k, v in old_d.items():
@@ -5,9 +5,20 @@ from .api import (
5
5
  M2MAddSchemaIn,
6
6
  M2MRemoveSchemaIn,
7
7
  M2MSchemaIn,
8
+ )
9
+ from .filters import (
8
10
  RelationFilterSchema,
11
+ MatchCaseFilterSchema,
12
+ MatchConditionFilterSchema,
13
+ BooleanMatchFilterSchema,
14
+ )
15
+ from .helpers import (
16
+ M2MRelationSchema,
17
+ QuerySchema,
18
+ ModelQuerySetSchema,
19
+ ObjectQuerySchema,
20
+ ObjectsQuerySchema,
9
21
  )
10
- from .helpers import M2MRelationSchema, QuerySchema, ModelQuerySetSchema, ObjectQuerySchema, ObjectsQuerySchema
11
22
 
12
23
  __all__ = [
13
24
  "GenericMessageSchema",
@@ -22,4 +33,7 @@ __all__ = [
22
33
  "ObjectQuerySchema",
23
34
  "ObjectsQuerySchema",
24
35
  "RelationFilterSchema",
36
+ "MatchCaseFilterSchema",
37
+ "MatchConditionFilterSchema",
38
+ "BooleanMatchFilterSchema",
25
39
  ]
ninja_aio/schemas/api.py CHANGED
@@ -1,5 +1,3 @@
1
- from typing import Any
2
-
3
1
  from ninja import Schema
4
2
 
5
3
 
@@ -24,20 +22,3 @@ class M2MRemoveSchemaIn(Schema):
24
22
  class M2MSchemaIn(Schema):
25
23
  add: list = []
26
24
  remove: list = []
27
-
28
-
29
- class RelationFilterSchema(Schema):
30
- """
31
- Schema for configuring relation-based filters in RelationFilterViewSetMixin.
32
-
33
- Attributes:
34
- filter_type: A tuple of (type, default_value) used to generate the query parameter
35
- field in the filters schema. Example: (int, None) for optional integer filter.
36
- query_param: The name of the query parameter exposed in the API endpoint.
37
- This is what clients will use in requests (e.g., ?author_id=5).
38
- query_filter: The Django ORM lookup to apply (e.g., "author__id", "category__slug").
39
- """
40
-
41
- filter_type: tuple[type, Any]
42
- query_param: str
43
- query_filter: str
@@ -0,0 +1,73 @@
1
+ from typing import Any
2
+
3
+ from ninja import Schema
4
+
5
+
6
+ class FilterSchema(Schema):
7
+ """
8
+ Schema for configuring basic filters in FilterViewSetMixin.
9
+
10
+ Attributes:
11
+ filter_type: A tuple of (type, default_value) used to generate the query parameter
12
+ field in the filters schema. Example: (str, None) for optional string filter.
13
+ query_param: The name of the query parameter exposed in the API endpoint.
14
+ This is what clients will use in requests (e.g., ?name=example).
15
+ """
16
+
17
+ filter_type: tuple[type, Any]
18
+ query_param: str
19
+
20
+
21
+ class MatchConditionFilterSchema(Schema):
22
+ """
23
+ Schema for configuring match condition filters in MatchConditionFilterViewSetMixin.
24
+ Attributes:
25
+ query_filter: The Django ORM lookup to apply (e.g., "status", "category__name").
26
+ include: Whether to include records matching the condition (default: True).
27
+ """
28
+ query_filter: dict[str, Any]
29
+ include: bool = True
30
+
31
+
32
+ class BooleanMatchFilterSchema(Schema):
33
+ """
34
+ Schema for configuring boolean match filters in BooleanMatchFilterViewSetMixin.
35
+
36
+ Attributes:
37
+ true: MatchConditionFilterSchema for when the boolean filter is True.
38
+ false: MatchConditionFilterSchema for when the boolean filter is False.
39
+ """
40
+
41
+ true: MatchConditionFilterSchema
42
+ false: MatchConditionFilterSchema
43
+
44
+
45
+ class RelationFilterSchema(FilterSchema):
46
+ """
47
+ Schema for configuring relation-based filters in RelationFilterViewSetMixin.
48
+
49
+ Attributes:
50
+ filter_type: A tuple of (type, default_value) used to generate the query parameter
51
+ field in the filters schema. Example: (int, None) for optional integer filter.
52
+ query_param: The name of the query parameter exposed in the API endpoint.
53
+ This is what clients will use in requests (e.g., ?author_id=5).
54
+ query_filter: The Django ORM lookup to apply (e.g., "author__id", "category__slug").
55
+ """
56
+
57
+ query_filter: str
58
+
59
+
60
+ class MatchCaseFilterSchema(FilterSchema):
61
+ """
62
+ Schema for configuring match-case filters in MatchCaseFilterViewSetMixin.
63
+
64
+ Attributes:
65
+ filter_type: A tuple of (type, default_value) used to generate the query parameter
66
+ field in the filters schema. Defaults to (bool, None) for optional boolean filter.
67
+ query_param: The name of the query parameter exposed in the API endpoint.
68
+ This is what clients will use in requests (e.g., ?is_active=true).
69
+ cases: A BooleanMatchFilterSchema defining the filter conditions for True and False cases.
70
+ """
71
+
72
+ filter_type: tuple[type, Any] = (bool, None)
73
+ cases: BooleanMatchFilterSchema
ninja_aio/views/api.py CHANGED
@@ -314,6 +314,14 @@ class APIViewSet(API):
314
314
  def _check_relations_filters(self, filter: str):
315
315
  return filter in getattr(self, "relations_filters_fields", [])
316
316
 
317
+ def _check_match_cases_filters(self, filter: str):
318
+ return filter in getattr(self, "filters_match_cases_fields", [])
319
+
320
+ def _is_special_filter(self, filter: str):
321
+ return self._check_relations_filters(filter) or self._check_match_cases_filters(
322
+ filter
323
+ )
324
+
317
325
  def _auth_view(self, view_type: str):
318
326
  """
319
327
  Resolve auth for a specific HTTP verb; falls back to self.auth if NOT_SET.
ninja_aio/views/mixins.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ninja_aio.views.api import APIViewSet
2
- from ninja_aio.schemas import RelationFilterSchema
2
+ from ninja_aio.schemas import RelationFilterSchema, MatchCaseFilterSchema
3
3
 
4
4
 
5
5
  class IcontainsFilterViewSetMixin(APIViewSet):
@@ -56,7 +56,7 @@ class IcontainsFilterViewSetMixin(APIViewSet):
56
56
  **{
57
57
  f"{key}__icontains": value
58
58
  for key, value in filters.items()
59
- if isinstance(value, str) and not self._check_relations_filters(key)
59
+ if isinstance(value, str) and not self._is_special_filter(key)
60
60
  }
61
61
  )
62
62
 
@@ -98,7 +98,7 @@ class BooleanFilterViewSetMixin(APIViewSet):
98
98
  **{
99
99
  key: value
100
100
  for key, value in filters.items()
101
- if isinstance(value, bool) and not self._check_relations_filters(key)
101
+ if isinstance(value, bool) and not self._is_special_filter(key)
102
102
  }
103
103
  )
104
104
 
@@ -140,8 +140,7 @@ class NumericFilterViewSetMixin(APIViewSet):
140
140
  **{
141
141
  key: value
142
142
  for key, value in filters.items()
143
- if isinstance(value, (int, float))
144
- and not self._check_relations_filters(key)
143
+ if isinstance(value, (int, float)) and not self._is_special_filter(key)
145
144
  }
146
145
  )
147
146
 
@@ -183,8 +182,7 @@ class DateFilterViewSetMixin(APIViewSet):
183
182
  **{
184
183
  f"{key}{self._compare_attr}": value
185
184
  for key, value in filters.items()
186
- if hasattr(value, "isoformat")
187
- and not self._check_relations_filters(key)
185
+ if hasattr(value, "isoformat") and not self._is_special_filter(key)
188
186
  }
189
187
  )
190
188
 
@@ -346,3 +344,70 @@ class RelationFilterViewSetMixin(APIViewSet):
346
344
  if value is not None:
347
345
  rel_filters[rel_filter.query_filter] = value
348
346
  return base_qs.filter(**rel_filters) if rel_filters else base_qs
347
+
348
+
349
+ class MatchCaseFilterViewSetMixin(APIViewSet):
350
+ """
351
+ Mixin providing match-case filtering for Django QuerySets.
352
+ This mixin applies filters based on boolean query parameters defined in
353
+ MatchCaseFilterSchema entries. Each entry specifies different filter conditions
354
+ for True and False cases.
355
+ Attributes:
356
+ filters_match_cases: List of MatchCaseFilterSchema defining the match-case filters.
357
+ Each schema specifies:
358
+ - query_param: The API query parameter name (e.g., "is_active")
359
+ - cases: A BooleanMatchFilterSchema with 'true' and 'false' MatchConditionFilterSchema
360
+ Example:
361
+ class UserViewSet(MatchCaseFilterViewSetMixin, APIViewSet):
362
+ filters_match_cases = [
363
+ MatchCaseFilterSchema(
364
+ query_param="is_active",
365
+ cases=BooleanMatchFilterSchema(
366
+ true=MatchConditionFilterSchema(
367
+ query_filter={"status": "active"},
368
+ include=True,
369
+ ),
370
+ false=MatchConditionFilterSchema(
371
+ query_filter={"status": "inactive"},
372
+ include=True,
373
+ ),
374
+ ),
375
+ ),
376
+ ]
377
+ # GET /users?is_active=true -> queryset.filter(status="active")
378
+ # GET /users?is_active=false -> queryset.filter(status="inactive")
379
+ Notes:
380
+ - If the query parameter is not provided, no filtering is applied for that case.
381
+ - This mixin automatically registers query_params from filters_match_cases.
382
+ """
383
+
384
+ filters_match_cases: list[MatchCaseFilterSchema] = []
385
+
386
+ def __init_subclass__(cls, **kwargs):
387
+ super().__init_subclass__(**kwargs)
388
+ cls.query_params = {
389
+ **cls.query_params,
390
+ **{
391
+ filter_match.query_param: filter_match.filter_type
392
+ for filter_match in cls.filters_match_cases
393
+ },
394
+ }
395
+
396
+ @property
397
+ def filters_match_cases_fields(self):
398
+ return [filter_match.query_param for filter_match in self.filters_match_cases]
399
+
400
+ async def query_params_handler(self, queryset, filters):
401
+ base_qs = await super().query_params_handler(queryset, filters)
402
+ for filter_match in self.filters_match_cases:
403
+ value = filters.get(filter_match.query_param)
404
+ if value is not None:
405
+ case_filter = (
406
+ filter_match.cases.true if value else filter_match.cases.false
407
+ )
408
+ lookup = case_filter.query_filter
409
+ if case_filter.include:
410
+ base_qs = base_qs.filter(**lookup)
411
+ else:
412
+ base_qs = base_qs.exclude(**lookup)
413
+ return base_qs