django-ninja-aio-crud 1.0.5__py3-none-any.whl → 2.0.0__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.
ninja_aio/renders.py CHANGED
@@ -4,11 +4,13 @@ from typing import Any
4
4
 
5
5
  import orjson
6
6
  from django.http import HttpRequest
7
+ from django.conf import settings
7
8
  from ninja.renderers import BaseRenderer
8
9
 
9
10
 
10
11
  class ORJSONRenderer(BaseRenderer):
11
12
  media_type = "application/json"
13
+ option = getattr(settings, "NINJA_AIO_ORJSON_RENDERER_OPTION", None)
12
14
 
13
15
  def render(self, request: HttpRequest, data: dict, *, response_status):
14
16
  try:
@@ -16,9 +18,13 @@ class ORJSONRenderer(BaseRenderer):
16
18
  for k, v in old_d.items():
17
19
  if isinstance(v, list):
18
20
  data |= {k: self.render_list(v)}
19
- return orjson.dumps(self.render_dict(data))
21
+ return self.dumps(self.render_dict(data))
20
22
  except AttributeError:
21
- return orjson.dumps(data)
23
+ return self.dumps(data)
24
+
25
+ @classmethod
26
+ def dumps(cls, data: dict) -> bytes:
27
+ return orjson.dumps(data, option=cls.option)
22
28
 
23
29
  @classmethod
24
30
  def render_list(cls, data: list[dict]) -> list[dict]:
@@ -0,0 +1,23 @@
1
+ from .generics import GenericMessageSchema
2
+ from .api import (
3
+ M2MDetailSchema,
4
+ M2MSchemaOut,
5
+ M2MAddSchemaIn,
6
+ M2MRemoveSchemaIn,
7
+ M2MSchemaIn,
8
+ )
9
+ from .helpers import M2MRelationSchema, QuerySchema, ModelQuerySetSchema, ObjectQuerySchema, ObjectsQuerySchema
10
+
11
+ __all__ = [
12
+ "GenericMessageSchema",
13
+ "M2MDetailSchema",
14
+ "M2MSchemaOut",
15
+ "M2MAddSchemaIn",
16
+ "M2MRemoveSchemaIn",
17
+ "M2MSchemaIn",
18
+ "M2MRelationSchema",
19
+ "QuerySchema",
20
+ "ModelQuerySetSchema",
21
+ "ObjectQuerySchema",
22
+ "ObjectsQuerySchema",
23
+ ]
@@ -0,0 +1,24 @@
1
+ from ninja import Schema
2
+
3
+
4
+ class M2MDetailSchema(Schema):
5
+ count: int
6
+ details: list[str]
7
+
8
+
9
+ class M2MSchemaOut(Schema):
10
+ errors: M2MDetailSchema
11
+ results: M2MDetailSchema
12
+
13
+
14
+ class M2MAddSchemaIn(Schema):
15
+ add: list = []
16
+
17
+
18
+ class M2MRemoveSchemaIn(Schema):
19
+ remove: list = []
20
+
21
+
22
+ class M2MSchemaIn(Schema):
23
+ add: list = []
24
+ remove: list = []
@@ -0,0 +1,5 @@
1
+ from pydantic import RootModel
2
+
3
+
4
+ class GenericMessageSchema(RootModel[dict[str, str]]):
5
+ root: dict[str, str]
@@ -1,36 +1,9 @@
1
- from typing import Optional, Type
1
+ from typing import List, Optional, Type
2
2
 
3
3
  from ninja import Schema
4
- from .models import ModelSerializer
4
+ from ninja_aio.types import ModelSerializerMeta
5
5
  from django.db.models import Model
6
- from pydantic import BaseModel, RootModel, ConfigDict, model_validator
7
-
8
-
9
- class GenericMessageSchema(RootModel[dict[str, str]]):
10
- root: dict[str, str]
11
-
12
-
13
- class M2MDetailSchema(Schema):
14
- count: int
15
- details: list[str]
16
-
17
-
18
- class M2MSchemaOut(Schema):
19
- errors: M2MDetailSchema
20
- results: M2MDetailSchema
21
-
22
-
23
- class M2MAddSchemaIn(Schema):
24
- add: list = []
25
-
26
-
27
- class M2MRemoveSchemaIn(Schema):
28
- remove: list = []
29
-
30
-
31
- class M2MSchemaIn(Schema):
32
- add: list = []
33
- remove: list = []
6
+ from pydantic import BaseModel, ConfigDict, model_validator
34
7
 
35
8
 
36
9
  class M2MRelationSchema(BaseModel):
@@ -55,7 +28,7 @@ class M2MRelationSchema(BaseModel):
55
28
  )
56
29
  """
57
30
 
58
- model: Type[ModelSerializer] | Type[Model]
31
+ model: ModelSerializerMeta | Type[Model]
59
32
  related_name: str
60
33
  add: bool = True
61
34
  remove: bool = True
@@ -74,9 +47,44 @@ class M2MRelationSchema(BaseModel):
74
47
  if related_schema is not None:
75
48
  return data
76
49
  model = data.get("model")
77
- if not issubclass(model, ModelSerializer):
50
+ if not isinstance(model, ModelSerializerMeta):
78
51
  raise ValueError(
79
52
  "related_schema must be provided if model is not a ModelSerializer",
80
53
  )
81
54
  data["related_schema"] = model.generate_related_s()
82
55
  return data
56
+
57
+
58
+ class ModelQuerySetSchema(BaseModel):
59
+ select_related: Optional[list[str]] = []
60
+ prefetch_related: Optional[list[str]] = []
61
+
62
+
63
+ class ModelQuerySetExtraSchema(ModelQuerySetSchema):
64
+ scope: str
65
+
66
+
67
+ class ObjectQuerySchema(ModelQuerySetSchema):
68
+ getters: Optional[dict] = {}
69
+
70
+
71
+ class ObjectsQuerySchema(ModelQuerySetSchema):
72
+ filters: Optional[dict] = {}
73
+
74
+
75
+ class QuerySchema(ModelQuerySetSchema):
76
+ filters: Optional[dict] = {}
77
+ getters: Optional[dict] = {}
78
+
79
+
80
+ class QueryUtilBaseScopesSchema(BaseModel):
81
+ READ: str = "read"
82
+ QUERYSET_REQUEST: str = "queryset_request"
83
+
84
+
85
+ class DecoratorsSchema(Schema):
86
+ list: Optional[List] = []
87
+ retrieve: Optional[List] = []
88
+ create: Optional[List] = []
89
+ update: Optional[List] = []
90
+ delete: Optional[List] = []
ninja_aio/views.py CHANGED
@@ -7,14 +7,16 @@ from django.http import HttpRequest
7
7
  from django.db.models import Model, QuerySet
8
8
  from pydantic import create_model
9
9
 
10
+ from ninja_aio.schemas.helpers import ModelQuerySetSchema, QuerySchema, DecoratorsSchema
11
+
10
12
  from .models import ModelSerializer, ModelUtil
11
13
  from .schemas import (
12
14
  GenericMessageSchema,
13
15
  M2MRelationSchema,
14
16
  )
15
- from .helpers import ManyToManyAPI
17
+ from .helpers.api import ManyToManyAPI
16
18
  from .types import ModelSerializerMeta, VIEW_TYPES
17
- from .decorators import unique_view
19
+ from .decorators import unique_view, decorate_view
18
20
 
19
21
  ERROR_CODES = frozenset({400, 401, 404, 428})
20
22
 
@@ -156,6 +158,7 @@ class APIViewSet:
156
158
 
157
159
  model: ModelSerializer | Model
158
160
  api: NinjaAPI
161
+ router_tag: str = ""
159
162
  schema_in: Schema | None = None
160
163
  schema_out: Schema | None = None
161
164
  schema_update: Schema | None = None
@@ -175,15 +178,22 @@ class APIViewSet:
175
178
  delete_docs = "Delete an object by its primary key."
176
179
  m2m_relations: list[M2MRelationSchema] = []
177
180
  m2m_auth: list | None = NOT_SET
181
+ extra_decorators: DecoratorsSchema = DecoratorsSchema()
178
182
 
179
183
  def __init__(self) -> None:
180
184
  self.error_codes = ERROR_CODES
181
- self.model_util = ModelUtil(self.model)
185
+ self.model_util = (
186
+ ModelUtil(self.model)
187
+ if not isinstance(self.model, ModelSerializerMeta)
188
+ else self.model.util
189
+ )
182
190
  self.schema_out, self.schema_in, self.schema_update = self.get_schemas()
183
191
  self.path_schema = self._generate_path_schema()
184
192
  self.filters_schema = self._generate_filters_schema()
185
193
  self.model_verbose_name = self.model._meta.verbose_name.capitalize()
186
- self.router_tag = self.model_verbose_name
194
+ self.router_tag = (
195
+ self.model_verbose_name if not self.router_tag else self.router_tag
196
+ )
187
197
  self.router = Router(tags=[self.router_tag])
188
198
  self.path = "/"
189
199
  self.get_path = ""
@@ -241,7 +251,7 @@ class APIViewSet:
241
251
  Schema containing only the primary key field for path resolution.
242
252
  """
243
253
  return self._generate_schema(
244
- {self.model_util.model_pk_name: (int | str, ...)}, "PathSchema"
254
+ {self.model_util.model_pk_name: self.model_util.pk_field_type}, "PathSchema"
245
255
  )
246
256
 
247
257
  def _generate_filters_schema(self):
@@ -256,6 +266,16 @@ class APIViewSet:
256
266
  """
257
267
  return data.model_dump()[self.model_util.model_pk_name]
258
268
 
269
+ def _get_query_data(self) -> ModelQuerySetSchema:
270
+ """
271
+ Return default query data for list/retrieve views.
272
+ """
273
+ return (
274
+ ModelQuerySetSchema()
275
+ if not isinstance(self.model, ModelSerializerMeta)
276
+ else self.model.query_util.read_config
277
+ )
278
+
259
279
  def get_schemas(self):
260
280
  """
261
281
  Return (schema_out, schema_in, schema_update), generating them if model is a ModelSerializer.
@@ -290,7 +310,7 @@ class APIViewSet:
290
310
  description=self.create_docs,
291
311
  response={201: self.schema_out, self.error_codes: GenericMessageSchema},
292
312
  )
293
- @unique_view(self)
313
+ @decorate_view(unique_view(self), *self.extra_decorators.create)
294
314
  async def create(request: HttpRequest, data: self.schema_in): # type: ignore
295
315
  return 201, await self.model_util.create_s(request, data, self.schema_out)
296
316
 
@@ -311,25 +331,23 @@ class APIViewSet:
311
331
  self.error_codes: GenericMessageSchema,
312
332
  },
313
333
  )
314
- @unique_view(self, plural=True)
315
- @paginate(self.pagination_class)
334
+ @decorate_view(
335
+ paginate(self.pagination_class),
336
+ unique_view(self, plural=True),
337
+ *self.extra_decorators.list,
338
+ )
316
339
  async def list(
317
340
  request: HttpRequest,
318
341
  filters: Query[self.filters_schema] = None, # type: ignore
319
342
  ):
320
- qs = self.model.objects.select_related()
321
- if isinstance(self.model, ModelSerializerMeta):
322
- qs = await self.model.queryset_request(request)
323
- rels = self.model_util.get_reverse_relations()
324
- if len(rels) > 0:
325
- qs = qs.prefetch_related(*rels)
343
+ qs = await self.model_util.get_objects(
344
+ request,
345
+ query_data=self._get_query_data(),
346
+ is_for_read=True,
347
+ )
326
348
  if filters is not None:
327
349
  qs = await self.query_params_handler(qs, filters.model_dump())
328
- objs = [
329
- await self.model_util.read_s(request, obj, self.schema_out)
330
- async for obj in qs.all()
331
- ]
332
- return objs
350
+ return await self.model_util.list_read_s(self.schema_out, request, qs)
333
351
 
334
352
  return list
335
353
 
@@ -345,10 +363,16 @@ class APIViewSet:
345
363
  description=self.retrieve_docs,
346
364
  response={200: self.schema_out, self.error_codes: GenericMessageSchema},
347
365
  )
348
- @unique_view(self)
366
+ @decorate_view(unique_view(self), *self.extra_decorators.retrieve)
349
367
  async def retrieve(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
350
- obj = await self.model_util.get_object(request, self._get_pk(pk))
351
- return await self.model_util.read_s(request, obj, self.schema_out)
368
+ query_data = self._get_query_data()
369
+ return await self.model_util.read_s(
370
+ self.schema_out,
371
+ request,
372
+ query_data=QuerySchema(
373
+ getters={"pk": self._get_pk(pk)}, **query_data.model_dump()
374
+ ),
375
+ )
352
376
 
353
377
  return retrieve
354
378
 
@@ -364,7 +388,7 @@ class APIViewSet:
364
388
  description=self.update_docs,
365
389
  response={200: self.schema_out, self.error_codes: GenericMessageSchema},
366
390
  )
367
- @unique_view(self)
391
+ @decorate_view(unique_view(self), *self.extra_decorators.update)
368
392
  async def update(
369
393
  request: HttpRequest,
370
394
  data: self.schema_update, # type: ignore
@@ -388,7 +412,7 @@ class APIViewSet:
388
412
  description=self.delete_docs,
389
413
  response={204: None, self.error_codes: GenericMessageSchema},
390
414
  )
391
- @unique_view(self)
415
+ @decorate_view(unique_view(self), *self.extra_decorators.delete)
392
416
  async def delete(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
393
417
  return 204, await self.model_util.delete_s(request, self._get_pk(pk))
394
418
 
@@ -1,17 +0,0 @@
1
- ninja_aio/__init__.py,sha256=_Euc356-ScYsaVY9gvZ7xLRgTfnHKXk0pv69eFhjdJg,119
2
- ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
3
- ninja_aio/auth.py,sha256=zUwruKcz7MXuOnWp5k1CCSwEc8s2Lyqqk7Qm9kPbJ3o,5149
4
- ninja_aio/decorators.py,sha256=LsvHbMxmw_So8NV0ey5NRRvSbfYkOZLeLQ4Fix7rQAY,5519
5
- ninja_aio/exceptions.py,sha256=1-iRbrloIyi0CR6Tcrn5YR4_LloA7PPohKIBaxXJ0-8,2596
6
- ninja_aio/models.py,sha256=fsFYjKFZb8MDpE1g3Nte-Dot4x7ofmuxIDi8qtkWu0o,35422
7
- ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
8
- ninja_aio/renders.py,sha256=5TdSQI8e4x3Gb2tAw1AaxrbU-asVjf2chWMr8x2Tt80,1485
9
- ninja_aio/schemas.py,sha256=sYxhovFq6nE_Gdx_yl1ufcuHBAXxpsnBmNytyTSqFLI,2449
10
- ninja_aio/types.py,sha256=TJSGlA7bt4g9fvPhJ7gzH5tKbLagPmZUzfgttEOp4xs,468
11
- ninja_aio/views.py,sha256=31pyOzABHwO1jcIVJRHXVrpMu9__FHmdX2vEhX6XrPs,15890
12
- ninja_aio/helpers/__init__.py,sha256=E45h2prtpCudx_bSKkIDMNK9oxrHPDJrweXWCuLxUOs,59
13
- ninja_aio/helpers/api.py,sha256=VW5C4hQl2flYhGPgVJ2ZkFK1tGMdNOaxVWJfZfLd7OY,17171
14
- django_ninja_aio_crud-1.0.5.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
15
- django_ninja_aio_crud-1.0.5.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
16
- django_ninja_aio_crud-1.0.5.dist-info/METADATA,sha256=V6cJgV_yDqLnEtG1FKbQR0fdmGE6okwPChkwEkyfNzs,8570
17
- django_ninja_aio_crud-1.0.5.dist-info/RECORD,,