django-ninja-aio-crud 0.6.0__py3-none-any.whl → 0.6.2__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.3
2
2
  Name: django-ninja-aio-crud
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
6
  Requires-Python: >=3.10
@@ -100,7 +100,7 @@ class Foo(ModelSerializer):
100
100
  fields = ["name", "bar"]
101
101
  ```
102
102
 
103
- - ReadSerializer, CreateSerializer, UpdateSerializer are used to define which fields would be included in runtime schemas creation. You can also specify custom fields and handle their function by overriding custom_actions ModelSerializer's method(custom fields are only available for Create and Update serializers).
103
+ - ReadSerializer, CreateSerializer, UpdateSerializer are used to define which fields would be included in runtime schemas creation. You can also specify custom fields and handle their function by overriding custom_actions ModelSerializer's method.
104
104
 
105
105
  ```python
106
106
  # models.py
@@ -157,7 +157,7 @@ class Foo(ModelSerializer):
157
157
  optionals = [("bar", str), ("active", bool)]
158
158
 
159
159
  class UpdateSerializer:
160
- optionals = [[("bar", str), ("active", bool)]
160
+ optionals = [("bar", str), ("active", bool)]
161
161
  ```
162
162
 
163
163
  - Instead of declaring your fields maybe you want to exclude some of them. Declaring "excludes" attribute into serializers will exclude the given fields. (You can declare only one between "fields" and "excludes").
@@ -182,10 +182,39 @@ class Foo(ModelSerializer):
182
182
 
183
183
  class UpdateSerializer:
184
184
  excludes = ["id", "name"]
185
- optionals = [[("bar", str), ("active", bool)]
185
+ optionals = [("bar", str), ("active", bool)]
186
+ ```
187
+ - If you want to add into ReadSerializer model properties you must define them as customs.
188
+ ```python
189
+ # models.py
190
+ from django.db import models
191
+ from ninja_aio.models import ModelSerializer
192
+
193
+
194
+ class Foo(ModelSerializer):
195
+ name = models.CharField(max_length=30)
196
+ bar = models.CharField(max_length=30, default="")
197
+ active = models.BooleanField(default=False)
198
+
199
+ @propetry
200
+ def full_name(self):
201
+ return f"{self.name} example_full_name"
202
+
203
+ class ReadSerializer:
204
+ excludes = ["bar"]
205
+ customs = [("full_name", str, "")]
206
+
207
+ class CreateSerializer:
208
+ fields = ["name"]
209
+ optionals = [("bar", str), ("active", bool)]
210
+
211
+ class UpdateSerializer:
212
+ excludes = ["id", "name"]
213
+ optionals = [("bar", str), ("active", bool)]
186
214
  ```
187
215
 
188
216
 
217
+
189
218
  ### APIViewSet
190
219
 
191
220
  - View class used to automatically generate CRUD views. in your views.py import APIViewSet and define your api using NinjaAIO class. NinjaAIO class uses built-in parser and renderer which use orjson for data serialization.
@@ -0,0 +1,13 @@
1
+ ninja_aio/__init__.py,sha256=fj_imNMQNt3s9V2z1MaTVCq27ZeyFxCD555vG2F80d8,119
2
+ ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
3
+ ninja_aio/auth.py,sha256=fKboioU4sezPukKJukIwiboxml_KV7irhCH3vGYt5pU,1008
4
+ ninja_aio/exceptions.py,sha256=gPnZX1Do2GXudbU8wDYkwhO70Qj0ZNrIJJ2UXRs9vYk,2241
5
+ ninja_aio/models.py,sha256=58Uk24f2IQYDdnGq1vXp8HqmCwDYeSkZtbiQ9JOQEXc,15504
6
+ ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
7
+ ninja_aio/renders.py,sha256=mHeKNJtmDhZmgFpS9B6SPn5uZFcyVXrsoMhr149LeW8,1555
8
+ ninja_aio/schemas.py,sha256=EgRkfhnzZqwGvdBmqlZixMtMcoD1ZxV_qzJ3fmaAy20,113
9
+ ninja_aio/types.py,sha256=EHznS-6KWLwSX5hLeXbAi7qHWla09_rGeQraiLpH-aY,491
10
+ ninja_aio/views.py,sha256=z820mKzfbvh9SZ4cALQVKMfc64kx74t8xWGWaUBzHfs,9270
11
+ django_ninja_aio_crud-0.6.2.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
12
+ django_ninja_aio_crud-0.6.2.dist-info/METADATA,sha256=QFGhtfwohjs0YQG1K6ysZpHQEYtfM0BTJaopwqaFmGQ,13740
13
+ django_ninja_aio_crud-0.6.2.dist-info/RECORD,,
ninja_aio/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django Ninja AIO CRUD - Rest Framework"""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.6.2"
4
4
 
5
5
  from .api import NinjaAIO
6
6
 
ninja_aio/auth.py CHANGED
@@ -1,8 +1,8 @@
1
- from joserfc import jwt, jwk, errors
1
+ from joserfc import jwt, jwk
2
2
  from django.http.request import HttpRequest
3
3
  from ninja.security.http import HttpBearer
4
4
 
5
- from .exceptions import AuthError, parse_jose_error
5
+ from .exceptions import AuthError
6
6
 
7
7
 
8
8
  class AsyncJwtBearer(HttpBearer):
@@ -16,15 +16,7 @@ class AsyncJwtBearer(HttpBearer):
16
16
 
17
17
  def validate_claims(self, claims: jwt.Claims):
18
18
  jwt_claims = self.get_claims()
19
-
20
- try:
21
- jwt_claims.validate(claims)
22
- except (
23
- errors.InvalidClaimError,
24
- errors.MissingClaimError,
25
- errors.ExpiredTokenError,
26
- ) as exc:
27
- raise AuthError(**parse_jose_error(exc), status_code=401)
19
+ jwt_claims.validate(claims)
28
20
 
29
21
  async def auth_handler(self, request: HttpRequest):
30
22
  """
@@ -35,8 +27,6 @@ class AsyncJwtBearer(HttpBearer):
35
27
  async def authenticate(self, request: HttpRequest, token: str):
36
28
  try:
37
29
  self.dcd = jwt.decode(token, self.jwt_public, algorithms=self.algorithms)
38
- except errors.BadSignatureError as exc:
39
- raise AuthError(**parse_jose_error(exc), status_code=401)
40
30
  except ValueError as exc:
41
31
  raise AuthError(", ".join(exc.args), 401)
42
32
 
ninja_aio/exceptions.py CHANGED
@@ -3,6 +3,7 @@ from functools import partial
3
3
  from joserfc.errors import JoseError
4
4
  from ninja import NinjaAPI
5
5
  from django.http import HttpRequest, HttpResponse
6
+ from pydantic import ValidationError
6
7
 
7
8
 
8
9
  class BaseException(Exception):
@@ -34,14 +35,37 @@ class AuthError(BaseException):
34
35
  pass
35
36
 
36
37
 
38
+ class PydanticValidationError(BaseException):
39
+ def __init__(self, details=None):
40
+ super().__init__("Validation Error", 400, details)
41
+
42
+
37
43
  def _default_error(
38
44
  request: HttpRequest, exc: BaseException, api: type[NinjaAPI]
39
45
  ) -> HttpResponse:
40
46
  return api.create_response(request, exc.error, status=exc.status_code)
41
47
 
42
48
 
49
+ def _pydantic_validation_error(
50
+ request: HttpRequest, exc: ValidationError, api: type[NinjaAPI]
51
+ ) -> HttpResponse:
52
+ error = PydanticValidationError(exc.errors(include_input=False))
53
+ return api.create_response(request, error.error, status=error.status_code)
54
+
55
+
56
+ def _jose_error(
57
+ request: HttpRequest, exc: JoseError, api: type[NinjaAPI]
58
+ ) -> HttpResponse:
59
+ error = BaseException(**parse_jose_error(exc), status_code=401)
60
+ return api.create_response(request, error.error, status=error.status_code)
61
+
62
+
43
63
  def set_api_exception_handlers(api: type[NinjaAPI]) -> None:
44
64
  api.add_exception_handler(BaseException, partial(_default_error, api=api))
65
+ api.add_exception_handler(JoseError, partial(_jose_error, api=api))
66
+ api.add_exception_handler(
67
+ ValidationError, partial(_pydantic_validation_error, api=api)
68
+ )
45
69
 
46
70
 
47
71
  def parse_jose_error(jose_exc: JoseError) -> dict:
ninja_aio/models.py CHANGED
@@ -122,7 +122,10 @@ class ModelUtil:
122
122
  try:
123
123
  field_obj = getattr(self.model, k).field
124
124
  except AttributeError:
125
- field_obj = getattr(self.model, k).related
125
+ try:
126
+ field_obj = getattr(self.model, k).related
127
+ except AttributeError:
128
+ pass
126
129
  if isinstance(v, dict) and (
127
130
  isinstance(field_obj, models.ForeignKey)
128
131
  or isinstance(field_obj, models.OneToOneField)
@@ -196,6 +199,7 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
196
199
  class ReadSerializer:
197
200
  fields: list[str] = []
198
201
  excludes: list[str] = []
202
+ customs: list[tuple[str, type, Any]] = []
199
203
 
200
204
  class UpdateSerializer:
201
205
  fields: list[str] = []
@@ -257,20 +261,23 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
257
261
  case "Patch":
258
262
  s_type = "update"
259
263
  case "Out":
260
- fields, reverse_rels, excludes = cls.get_schema_out_data()
261
- if not fields and not reverse_rels and not excludes:
264
+ fields, reverse_rels, excludes, customs = cls.get_schema_out_data()
265
+ if not fields and not reverse_rels and not excludes and not customs:
262
266
  return None
263
267
  return create_schema(
264
268
  model=cls,
265
269
  name=f"{cls._meta.model_name}SchemaOut",
266
270
  depth=depth,
267
271
  fields=fields,
268
- custom_fields=reverse_rels,
272
+ custom_fields=reverse_rels + customs,
269
273
  exclude=excludes,
270
274
  )
271
275
  fields = cls.get_fields(s_type)
272
- customs = cls.get_custom_fields(s_type) + cls.get_optional_fields(s_type)
276
+ optionals = cls.get_optional_fields(s_type)
277
+ customs = cls.get_custom_fields(s_type) + optionals
273
278
  excludes = cls.get_excluded_fields(s_type)
279
+ if not fields and not excludes:
280
+ fields = [f[0] for f in optionals]
274
281
  return (
275
282
  create_schema(
276
283
  model=cls,
@@ -364,25 +371,36 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
364
371
  reverse_rels = []
365
372
  for f in cls.get_fields("read"):
366
373
  field_obj = getattr(cls, f)
367
- if isinstance(field_obj, ManyToManyDescriptor):
368
- rel_obj: ModelSerializer = field_obj.field.related_model
369
- if field_obj.reverse:
370
- rel_obj: ModelSerializer = field_obj.field.model
371
- rel_data = cls.get_reverse_relation_schema(rel_obj, "many", f)
372
- reverse_rels.append(rel_data)
373
- continue
374
- if isinstance(field_obj, ReverseManyToOneDescriptor):
375
- rel_obj: ModelSerializer = field_obj.field.model
376
- rel_data = cls.get_reverse_relation_schema(rel_obj, "many", f)
377
- reverse_rels.append(rel_data)
378
- continue
379
- if isinstance(field_obj, ReverseOneToOneDescriptor):
380
- rel_obj: ModelSerializer = field_obj.related.related_model
381
- rel_data = cls.get_reverse_relation_schema(rel_obj, "one", f)
374
+ if isinstance(
375
+ field_obj,
376
+ (
377
+ ManyToManyDescriptor,
378
+ ReverseManyToOneDescriptor,
379
+ ReverseOneToOneDescriptor,
380
+ ),
381
+ ):
382
+ if isinstance(field_obj, ManyToManyDescriptor):
383
+ rel_obj: ModelSerializer = field_obj.field.related_model
384
+ if field_obj.reverse:
385
+ rel_obj = field_obj.field.model
386
+ rel_type = "many"
387
+ elif isinstance(field_obj, ReverseManyToOneDescriptor):
388
+ rel_obj = field_obj.field.model
389
+ rel_type = "many"
390
+ else: # ReverseOneToOneDescriptor
391
+ rel_obj = field_obj.related.related_model
392
+ rel_type = "one"
393
+
394
+ rel_data = cls.get_reverse_relation_schema(rel_obj, rel_type, f)
382
395
  reverse_rels.append(rel_data)
383
396
  continue
384
397
  fields.append(f)
385
- return fields, reverse_rels, cls.get_excluded_fields("read")
398
+ return (
399
+ fields,
400
+ reverse_rels,
401
+ cls.get_excluded_fields("read"),
402
+ cls.get_custom_fields("read"),
403
+ )
386
404
 
387
405
  @classmethod
388
406
  def is_custom(cls, field: str):
ninja_aio/views.py CHANGED
@@ -112,6 +112,9 @@ class APIViewSet:
112
112
  def _generate_filters_schema(self):
113
113
  return self._generate_schema(self.query_params, "FiltersSchema")
114
114
 
115
+ def _get_pk(self, data: Schema):
116
+ return data.model_dump()[self.model_util.model_pk_name]
117
+
115
118
  def get_schemas(self):
116
119
  if isinstance(self.model, ModelSerializerMeta):
117
120
  return (
@@ -153,14 +156,17 @@ class APIViewSet:
153
156
  },
154
157
  )
155
158
  @paginate(self.pagination_class)
156
- async def list(request: HttpRequest, filters: Query[self.filters_schema]):
159
+ async def list(
160
+ request: HttpRequest, filters: Query[self.filters_schema] = None
161
+ ):
157
162
  qs = self.model.objects.select_related()
158
163
  if isinstance(self.model, ModelSerializerMeta):
159
164
  qs = await self.model.queryset_request(request)
160
165
  rels = self.model_util.get_reverse_relations()
161
166
  if len(rels) > 0:
162
167
  qs = qs.prefetch_related(*rels)
163
- qs = await self.query_params_handler(qs, filters.model_dump())
168
+ if filters is not None:
169
+ qs = await self.query_params_handler(qs, filters.model_dump())
164
170
  objs = [
165
171
  await self.model_util.read_s(request, obj, self.schema_out)
166
172
  async for obj in qs.all()
@@ -177,7 +183,7 @@ class APIViewSet:
177
183
  response={200: self.schema_out, self.error_codes: GenericMessageSchema},
178
184
  )
179
185
  async def retrieve(request: HttpRequest, pk: Path[self.path_schema]):
180
- obj = await self.model_util.get_object(request, pk)
186
+ obj = await self.model_util.get_object(request, self._get_pk(pk))
181
187
  return await self.model_util.read_s(request, obj, self.schema_out)
182
188
 
183
189
  retrieve.__name__ = f"retrieve_{self.model_util.model_name}"
@@ -192,7 +198,9 @@ class APIViewSet:
192
198
  async def update(
193
199
  request: HttpRequest, data: self.schema_update, pk: Path[self.path_schema]
194
200
  ):
195
- return await self.model_util.update_s(request, data, pk, self.schema_out)
201
+ return await self.model_util.update_s(
202
+ request, data, self._get_pk(pk), self.schema_out
203
+ )
196
204
 
197
205
  update.__name__ = f"update_{self.model_util.model_name}"
198
206
  return update
@@ -204,7 +212,7 @@ class APIViewSet:
204
212
  response={204: None, self.error_codes: GenericMessageSchema},
205
213
  )
206
214
  async def delete(request: HttpRequest, pk: Path[self.path_schema]):
207
- return 204, await self.model_util.delete_s(request, pk)
215
+ return 204, await self.model_util.delete_s(request, self._get_pk(pk))
208
216
 
209
217
  delete.__name__ = f"delete_{self.model_util.model_name}"
210
218
  return delete
@@ -1,13 +0,0 @@
1
- ninja_aio/__init__.py,sha256=-m_l3-9_2Q1-PvkjWqAfSmu2jt2ILNb490jT2yC4sbk,119
2
- ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
3
- ninja_aio/auth.py,sha256=z9gniLIgT8SjRqhGN7ZI0AGHjsALwgU6eyr2m46fwFY,1389
4
- ninja_aio/exceptions.py,sha256=JGUvZyYevGnFYFk2JYnNqng1e9ilG8l325wA5YC1RUA,1364
5
- ninja_aio/models.py,sha256=ddV2QGzWSJzwZ4vt-AJ9rRdiQCVSEtl6Bj3mxroSiQE,15096
6
- ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
7
- ninja_aio/renders.py,sha256=mHeKNJtmDhZmgFpS9B6SPn5uZFcyVXrsoMhr149LeW8,1555
8
- ninja_aio/schemas.py,sha256=EgRkfhnzZqwGvdBmqlZixMtMcoD1ZxV_qzJ3fmaAy20,113
9
- ninja_aio/types.py,sha256=EHznS-6KWLwSX5hLeXbAi7qHWla09_rGeQraiLpH-aY,491
10
- ninja_aio/views.py,sha256=b-KLPkNhfMXsTx4egfi-nKeuKqYEUOLXJFNt0fYariE,9027
11
- django_ninja_aio_crud-0.6.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
12
- django_ninja_aio_crud-0.6.0.dist-info/METADATA,sha256=aRvvPaOFsyvqGwg_wCpoQJ0PiQJfo0obaBPlovRlXeA,13078
13
- django_ninja_aio_crud-0.6.0.dist-info/RECORD,,