django-ninja-aio-crud 2.9.0__py3-none-any.whl → 2.10.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.

Potentially problematic release.


This version of django-ninja-aio-crud might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ninja-aio-crud
3
- Version: 2.9.0
3
+ Version: 2.10.1
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
6
  Requires-Python: >=3.10, <3.15
@@ -1,4 +1,4 @@
1
- ninja_aio/__init__.py,sha256=YE75I1ewNyWylWEf1tZjFFmQlFLdsdYyYiQgxSr1Jdk,119
1
+ ninja_aio/__init__.py,sha256=89U1Dj8vplVSbgWDcQsklzcAzW070HQlarC0u9_LOX4,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
@@ -15,15 +15,15 @@ ninja_aio/helpers/api.py,sha256=2beyexep-ehgaA_1bV5Yuh3zRDVcRCMkrW94nmfDWEA,2081
15
15
  ninja_aio/helpers/query.py,sha256=NMGkS_v-ZVYKNtf1XohEUzfwca52Eq5FTcQ5lehHjus,4682
16
16
  ninja_aio/models/__init__.py,sha256=L3UQnQAlKoI3F7jinadL-Nn55hkPvnSRPYW0JtnbWFo,114
17
17
  ninja_aio/models/serializers.py,sha256=I7pUz_vl0FElaVsrGaogT_Lj9T4uaNG7UDMGf5VMwW4,38468
18
- ninja_aio/models/utils.py,sha256=N7waJGOCsoFvbbO1xXy94acKeG85WRK3hjZJz7IukhU,32885
19
- ninja_aio/schemas/__init__.py,sha256=iLBwHg0pmL9k_UkIui5Q8QIl_gO4fgxSv2JHxDzqnSI,549
20
- ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
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
21
21
  ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
22
22
  ninja_aio/schemas/helpers.py,sha256=Vti5BfHWpxaJXj_ixZBJb34VRwhHODrlVjRlIuHh_ug,8428
23
23
  ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
24
- ninja_aio/views/api.py,sha256=CW74JB1tsdWfv3Y3x4K9o8yXsrMxCKWZ4QalhqJGan8,22072
25
- ninja_aio/views/mixins.py,sha256=Jh6BG8Cs823nurVlODlzCquTxKrLH7Pmo5udPqUGZek,11378
26
- django_ninja_aio_crud-2.9.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
27
- django_ninja_aio_crud-2.9.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
28
- django_ninja_aio_crud-2.9.0.dist-info/METADATA,sha256=7hbmX5K1ixoBn0RnFlkpnp3runSgZ1pi70v606cUX3Q,9963
29
- django_ninja_aio_crud-2.9.0.dist-info/RECORD,,
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,,
ninja_aio/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django Ninja AIO CRUD - Rest Framework"""
2
2
 
3
- __version__ = "2.9.0"
3
+ __version__ = "2.10.1"
4
4
 
5
5
  from .api import NinjaAIO
6
6
 
ninja_aio/models/utils.py CHANGED
@@ -566,10 +566,10 @@ class ModelUtil:
566
566
  serializable_fields = self._get_serializable_field_names(is_for)
567
567
  for f in serializable_fields:
568
568
  field_obj = getattr(self.model, f)
569
- if isinstance(field_obj, ForwardManyToOneDescriptor):
569
+ if isinstance(field_obj, ForwardOneToOneDescriptor):
570
570
  select_rels.append(f)
571
571
  continue
572
- if isinstance(field_obj, ForwardOneToOneDescriptor):
572
+ if isinstance(field_obj, ForwardManyToOneDescriptor):
573
573
  select_rels.append(f)
574
574
  return select_rels
575
575
 
@@ -5,6 +5,7 @@ from .api import (
5
5
  M2MAddSchemaIn,
6
6
  M2MRemoveSchemaIn,
7
7
  M2MSchemaIn,
8
+ RelationFilterSchema,
8
9
  )
9
10
  from .helpers import M2MRelationSchema, QuerySchema, ModelQuerySetSchema, ObjectQuerySchema, ObjectsQuerySchema
10
11
 
@@ -20,4 +21,5 @@ __all__ = [
20
21
  "ModelQuerySetSchema",
21
22
  "ObjectQuerySchema",
22
23
  "ObjectsQuerySchema",
24
+ "RelationFilterSchema",
23
25
  ]
ninja_aio/schemas/api.py CHANGED
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  from ninja import Schema
2
4
 
3
5
 
@@ -21,4 +23,21 @@ class M2MRemoveSchemaIn(Schema):
21
23
 
22
24
  class M2MSchemaIn(Schema):
23
25
  add: list = []
24
- remove: list = []
26
+ 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
ninja_aio/views/api.py CHANGED
@@ -311,6 +311,9 @@ class APIViewSet(API):
311
311
  "delete": (None, self.delete_view),
312
312
  }
313
313
 
314
+ def _check_relations_filters(self, filter: str):
315
+ return filter in getattr(self, "relations_filters_fields", [])
316
+
314
317
  def _auth_view(self, view_type: str):
315
318
  """
316
319
  Resolve auth for a specific HTTP verb; falls back to self.auth if NOT_SET.
ninja_aio/views/mixins.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from ninja_aio.views.api import APIViewSet
2
+ from ninja_aio.schemas import RelationFilterSchema
2
3
 
3
4
 
4
5
  class IcontainsFilterViewSetMixin(APIViewSet):
@@ -55,7 +56,7 @@ class IcontainsFilterViewSetMixin(APIViewSet):
55
56
  **{
56
57
  f"{key}__icontains": value
57
58
  for key, value in filters.items()
58
- if isinstance(value, str)
59
+ if isinstance(value, str) and not self._check_relations_filters(key)
59
60
  }
60
61
  )
61
62
 
@@ -94,7 +95,11 @@ class BooleanFilterViewSetMixin(APIViewSet):
94
95
  """
95
96
  base_qs = await super().query_params_handler(queryset, filters)
96
97
  return base_qs.filter(
97
- **{key: value for key, value in filters.items() if isinstance(value, bool)}
98
+ **{
99
+ key: value
100
+ for key, value in filters.items()
101
+ if isinstance(value, bool) and not self._check_relations_filters(key)
102
+ }
98
103
  )
99
104
 
100
105
 
@@ -136,6 +141,7 @@ class NumericFilterViewSetMixin(APIViewSet):
136
141
  key: value
137
142
  for key, value in filters.items()
138
143
  if isinstance(value, (int, float))
144
+ and not self._check_relations_filters(key)
139
145
  }
140
146
  )
141
147
 
@@ -178,6 +184,7 @@ class DateFilterViewSetMixin(APIViewSet):
178
184
  f"{key}{self._compare_attr}": value
179
185
  for key, value in filters.items()
180
186
  if hasattr(value, "isoformat")
187
+ and not self._check_relations_filters(key)
181
188
  }
182
189
  )
183
190
 
@@ -273,3 +280,69 @@ class LessEqualDateFilterViewSetMixin(DateFilterViewSetMixin):
273
280
  """
274
281
 
275
282
  _compare_attr = "__lte"
283
+
284
+
285
+ class RelationFilterViewSetMixin(APIViewSet):
286
+ """
287
+ Mixin providing filtering for related fields in Django QuerySets.
288
+
289
+ This mixin applies filters on related fields based on configured RelationFilterSchema
290
+ entries. Each entry maps a query parameter name to a Django ORM lookup path.
291
+
292
+ Attributes:
293
+ relations_filters: List of RelationFilterSchema defining the relation filters.
294
+ Each schema specifies:
295
+ - query_param: The API query parameter name (e.g., "author_id")
296
+ - query_filter: The Django ORM lookup (e.g., "author__id")
297
+ - filter_type: Tuple of (type, default) for schema generation
298
+
299
+ Example:
300
+ class BookViewSet(RelationFilterViewSetMixin, APIViewSet):
301
+ relations_filters = [
302
+ RelationFilterSchema(
303
+ query_param="author_id",
304
+ query_filter="author__id",
305
+ filter_type=(int, None),
306
+ ),
307
+ RelationFilterSchema(
308
+ query_param="category_slug",
309
+ query_filter="category__slug",
310
+ filter_type=(str, None),
311
+ ),
312
+ ]
313
+
314
+ # GET /books?author_id=5 -> queryset.filter(author__id=5)
315
+ # GET /books?category_slug=fiction -> queryset.filter(category__slug="fiction")
316
+
317
+ Notes:
318
+ - Filter values that are None or falsy are skipped.
319
+ - This mixin automatically registers query_params from relations_filters.
320
+ """
321
+
322
+ relations_filters: list[RelationFilterSchema] = []
323
+
324
+ def __init_subclass__(cls, **kwargs):
325
+ super().__init_subclass__(**kwargs)
326
+ cls.query_params = {
327
+ **cls.query_params,
328
+ **{
329
+ rel_filter.query_param: rel_filter.filter_type
330
+ for rel_filter in cls.relations_filters
331
+ },
332
+ }
333
+
334
+ @property
335
+ def relations_filters_fields(self):
336
+ return [rel_filter.query_param for rel_filter in self.relations_filters]
337
+
338
+ async def query_params_handler(self, queryset, filters):
339
+ """
340
+ Apply relation filters to the queryset based on configured relations_filters.
341
+ """
342
+ base_qs = await super().query_params_handler(queryset, filters)
343
+ rel_filters = {}
344
+ for rel_filter in self.relations_filters:
345
+ value = filters.get(rel_filter.query_param)
346
+ if value is not None:
347
+ rel_filters[rel_filter.query_filter] = value
348
+ return base_qs.filter(**rel_filters) if rel_filters else base_qs