django-ninja-aio-crud 2.16.2__py3-none-any.whl → 2.18.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.
- django_ninja_aio_crud-2.18.0.dist-info/METADATA +425 -0
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/RECORD +6 -6
- ninja_aio/__init__.py +1 -1
- ninja_aio/models/serializers.py +637 -74
- django_ninja_aio_crud-2.16.2.dist-info/METADATA +0 -379
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 2.16.2
|
|
4
|
-
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
|
-
Author: Giuseppe Casillo
|
|
6
|
-
Requires-Python: >=3.10, <3.15
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Classifier: Operating System :: OS Independent
|
|
9
|
-
Classifier: Topic :: Internet
|
|
10
|
-
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
11
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
-
Classifier: Topic :: Software Development
|
|
14
|
-
Classifier: Environment :: Web Environment
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
-
Classifier: Framework :: Django
|
|
24
|
-
Classifier: Framework :: AsyncIO
|
|
25
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
26
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
Requires-Dist: django-ninja >=1.3.0, <1.6
|
|
29
|
-
Requires-Dist: joserfc >=1.0.0, <= 1.4.1
|
|
30
|
-
Requires-Dist: orjson >= 3.10.7, <= 3.11.5
|
|
31
|
-
Requires-Dist: coverage ; extra == "test"
|
|
32
|
-
Project-URL: Documentation, https://django-ninja-aio.com
|
|
33
|
-
Project-URL: Repository, https://github.com/caspel26/django-ninja-aio-crud
|
|
34
|
-
Provides-Extra: test
|
|
35
|
-
|
|
36
|
-
# 🥷 django-ninja-aio-crud
|
|
37
|
-
|
|
38
|
-

|
|
39
|
-
[](https://sonarcloud.io/summary/new_code?id=caspel26_django-ninja-aio-crud)
|
|
40
|
-
[](https://codecov.io/gh/caspel26/django-ninja-aio-crud/)
|
|
41
|
-
[](https://pypi.org/project/django-ninja-aio-crud/)
|
|
42
|
-
[](LICENSE)
|
|
43
|
-
[](https://github.com/astral-sh/ruff)
|
|
44
|
-
|
|
45
|
-
> Lightweight async CRUD layer on top of **[Django Ninja](https://django-ninja.dev/)** with automatic schema generation, filtering, pagination, auth & Many‑to‑Many management.
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## ✨ Features
|
|
50
|
-
|
|
51
|
-
- Serializer (Meta-driven) first-class: dynamic schemas for existing Django models without inheriting ModelSerializer
|
|
52
|
-
- Async CRUD ViewSets (create, list, retrieve, update, delete)
|
|
53
|
-
- Automatic Pydantic schemas from `ModelSerializer` (read/create/update)
|
|
54
|
-
- Dynamic query params (runtime schema via `pydantic.create_model`)
|
|
55
|
-
- Per-method authentication (`auth`, `get_auth`, `post_auth`, etc.)
|
|
56
|
-
- Async pagination (customizable)
|
|
57
|
-
- M2M relation endpoints via `M2MRelationSchema` (add/remove/get + filters)
|
|
58
|
-
- Reverse relation serialization
|
|
59
|
-
- Hook methods (`query_params_handler`, `<related>_query_params_handler`, `custom_actions`, lifecycle hooks)
|
|
60
|
-
- ORJSON renderer through `NinjaAIO`
|
|
61
|
-
- Clean, minimal integration
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## 🚀 Quick Start (Serializer)
|
|
66
|
-
|
|
67
|
-
If you already have Django models, start with the Meta-driven Serializer for instant CRUD without changing model base classes.
|
|
68
|
-
|
|
69
|
-
```python
|
|
70
|
-
from ninja_aio.models import serializers
|
|
71
|
-
from ninja_aio.views import APIViewSet
|
|
72
|
-
from ninja_aio import NinjaAIO
|
|
73
|
-
from . import models
|
|
74
|
-
|
|
75
|
-
class BookSerializer(serializers.Serializer):
|
|
76
|
-
class Meta:
|
|
77
|
-
model = models.Book
|
|
78
|
-
schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
|
|
79
|
-
schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
|
|
80
|
-
schema_update = serializers.SchemaModelConfig(optionals=[("title", str), ("published", bool)])
|
|
81
|
-
|
|
82
|
-
api = NinjaAIO()
|
|
83
|
-
|
|
84
|
-
@api.viewset(models.Book)
|
|
85
|
-
class BookViewSet(APIViewSet):
|
|
86
|
-
serializer_class = BookSerializer
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Visit `/docs` → CRUD endpoints ready.
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## 🚀 Quick Start (ModelSerializer)
|
|
94
|
-
|
|
95
|
-
models.py
|
|
96
|
-
|
|
97
|
-
```python
|
|
98
|
-
from django.db import models
|
|
99
|
-
from ninja_aio.models import ModelSerializer
|
|
100
|
-
|
|
101
|
-
class Book(ModelSerializer):
|
|
102
|
-
title = models.CharField(max_length=120)
|
|
103
|
-
published = models.BooleanField(default=True)
|
|
104
|
-
|
|
105
|
-
class ReadSerializer:
|
|
106
|
-
fields = ["id", "title", "published"]
|
|
107
|
-
|
|
108
|
-
class CreateSerializer:
|
|
109
|
-
fields = ["title", "published"]
|
|
110
|
-
|
|
111
|
-
class UpdateSerializer:
|
|
112
|
-
optionals = [("title", str), ("published", bool)]
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
views.py
|
|
116
|
-
|
|
117
|
-
```python
|
|
118
|
-
from ninja_aio import NinjaAIO
|
|
119
|
-
from ninja_aio.views import APIViewSet
|
|
120
|
-
from .models import Book
|
|
121
|
-
|
|
122
|
-
api = NinjaAIO()
|
|
123
|
-
|
|
124
|
-
@api.viewset(Book)
|
|
125
|
-
class BookViewSet(APIViewSet):
|
|
126
|
-
pass
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Visit `/docs` → CRUD endpoints ready.
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 🔄 Query Filtering
|
|
135
|
-
|
|
136
|
-
```python
|
|
137
|
-
@api.viewset(Book)
|
|
138
|
-
class BookViewSet(APIViewSet):
|
|
139
|
-
query_params = {"published": (bool, None), "title": (str, None)}
|
|
140
|
-
|
|
141
|
-
async def query_params_handler(self, queryset, filters):
|
|
142
|
-
if filters.get("published") is not None:
|
|
143
|
-
queryset = queryset.filter(published=filters["published"])
|
|
144
|
-
if filters.get("title"):
|
|
145
|
-
queryset = queryset.filter(title__icontains=filters["title"])
|
|
146
|
-
return queryset
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Request:
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
GET /book/?published=true&title=python
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## 🤝 Many-to-Many Example (with filters)
|
|
158
|
-
|
|
159
|
-
```python
|
|
160
|
-
from ninja_aio.schemas import M2MRelationSchema
|
|
161
|
-
|
|
162
|
-
class Tag(ModelSerializer):
|
|
163
|
-
name = models.CharField(max_length=50)
|
|
164
|
-
class ReadSerializer:
|
|
165
|
-
fields = ["id", "name"]
|
|
166
|
-
|
|
167
|
-
class Article(ModelSerializer):
|
|
168
|
-
title = models.CharField(max_length=120)
|
|
169
|
-
tags = models.ManyToManyField(Tag, related_name="articles")
|
|
170
|
-
|
|
171
|
-
class ReadSerializer:
|
|
172
|
-
fields = ["id", "title", "tags"]
|
|
173
|
-
|
|
174
|
-
@api.viewset(Article)
|
|
175
|
-
class ArticleViewSet(APIViewSet):
|
|
176
|
-
m2m_relations = [
|
|
177
|
-
M2MRelationSchema(
|
|
178
|
-
model=Tag,
|
|
179
|
-
related_name="tags",
|
|
180
|
-
filters={"name": (str, "")}
|
|
181
|
-
)
|
|
182
|
-
]
|
|
183
|
-
|
|
184
|
-
async def tags_query_params_handler(self, queryset, filters):
|
|
185
|
-
n = filters.get("name")
|
|
186
|
-
if n:
|
|
187
|
-
queryset = queryset.filter(name__icontains=n)
|
|
188
|
-
return queryset
|
|
189
|
-
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Endpoints:
|
|
193
|
-
|
|
194
|
-
- `GET /article/{pk}/tag?name=dev`
|
|
195
|
-
- `POST /article/{pk}/tag/` body: `{"add":[1,2],"remove":[3]}`
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## 🔐 Authentication (JWT example)
|
|
200
|
-
|
|
201
|
-
```python
|
|
202
|
-
from ninja_aio.auth import AsyncJwtBearer
|
|
203
|
-
from joserfc import jwk
|
|
204
|
-
from .models import Book
|
|
205
|
-
|
|
206
|
-
PUBLIC_KEY = "-----BEGIN PUBLIC KEY----- ..."
|
|
207
|
-
|
|
208
|
-
class JWTAuth(AsyncJwtBearer):
|
|
209
|
-
jwt_public = jwk.RSAKey.import_key(PUBLIC_KEY)
|
|
210
|
-
jwt_alg = "RS256"
|
|
211
|
-
claims = {"sub": {"essential": True}}
|
|
212
|
-
|
|
213
|
-
async def auth_handler(self, request):
|
|
214
|
-
book_id = self.dcd.claims.get("sub")
|
|
215
|
-
return await Book.objects.aget(id=book_id)
|
|
216
|
-
|
|
217
|
-
@api.viewset(Book)
|
|
218
|
-
class SecureBookViewSet(APIViewSet):
|
|
219
|
-
auth = [JWTAuth()]
|
|
220
|
-
get_auth = None # list/retrieve public
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 📑 Lifecycle Hooks (ModelSerializer)
|
|
226
|
-
|
|
227
|
-
Available on every save/delete:
|
|
228
|
-
|
|
229
|
-
- `on_create_before_save`
|
|
230
|
-
- `on_create_after_save`
|
|
231
|
-
- `before_save`
|
|
232
|
-
- `after_save`
|
|
233
|
-
- `on_delete`
|
|
234
|
-
- `custom_actions(payload)` (create/update custom field logic)
|
|
235
|
-
- `post_create()` (after create commit)
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## 🧩 Adding Custom Endpoints
|
|
240
|
-
|
|
241
|
-
```python
|
|
242
|
-
from ninja_aio.decorators import api_get
|
|
243
|
-
|
|
244
|
-
@api.viewset(Book)
|
|
245
|
-
class BookViewSet(APIViewSet):
|
|
246
|
-
@api_get("/stats/")
|
|
247
|
-
async def stats(self, request):
|
|
248
|
-
total = await Book.objects.acount()
|
|
249
|
-
return {"total": total}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Or
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
@api.viewset(Book)
|
|
256
|
-
class BookViewSet(APIViewSet):
|
|
257
|
-
def views(self):
|
|
258
|
-
@self.router.get("/stats/")
|
|
259
|
-
async def stats(request):
|
|
260
|
-
total = await Book.objects.acount()
|
|
261
|
-
return {"total": total}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## 📄 Pagination
|
|
267
|
-
|
|
268
|
-
Default: `PageNumberPagination`. Override:
|
|
269
|
-
|
|
270
|
-
```python
|
|
271
|
-
from ninja.pagination import PageNumberPagination
|
|
272
|
-
|
|
273
|
-
class LargePagination(PageNumberPagination):
|
|
274
|
-
page_size = 50
|
|
275
|
-
max_page_size = 200
|
|
276
|
-
|
|
277
|
-
@api.viewset(Book)
|
|
278
|
-
class BookViewSet(APIViewSet):
|
|
279
|
-
pagination_class = LargePagination
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## Meta-driven Serializer (for vanilla Django models)
|
|
285
|
-
|
|
286
|
-
Moved above as the primary quick start.
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## 🛠 Project Structure & Docs
|
|
291
|
-
|
|
292
|
-
Documentation (MkDocs + Material):
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
docs/
|
|
296
|
-
getting_started/
|
|
297
|
-
tutorial/
|
|
298
|
-
api/
|
|
299
|
-
views/
|
|
300
|
-
models/
|
|
301
|
-
authentication.md
|
|
302
|
-
pagination.md
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Browse full reference:
|
|
306
|
-
|
|
307
|
-
- APIViewSet: `docs/api/views/api_view_set.md`
|
|
308
|
-
- APIView: `docs/api/views/api_view.md`
|
|
309
|
-
- ModelSerializer: `docs/api/models/model_serializer.md`
|
|
310
|
-
- Authentication: `docs/api/authentication.md`
|
|
311
|
-
- Example repository: https://github.com/caspel26/ninja-aio-blog-example
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
|
|
315
|
-
## 🧪 Tests
|
|
316
|
-
|
|
317
|
-
Use Django test runner + async ORM patterns. Example async pattern:
|
|
318
|
-
|
|
319
|
-
```python
|
|
320
|
-
obj = await Book.objects.acreate(title="T1", published=True)
|
|
321
|
-
count = await Book.objects.acount()
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## 🚫 Disable Operations
|
|
327
|
-
|
|
328
|
-
```python
|
|
329
|
-
@api.viewset(Book)
|
|
330
|
-
class ReadOnlyBookViewSet(APIViewSet):
|
|
331
|
-
disable = ["update", "delete"]
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
## 📌 Performance Tips
|
|
337
|
-
|
|
338
|
-
- Use `queryset_request` classmethod to prefetch
|
|
339
|
-
- Index frequently filtered fields
|
|
340
|
-
- Keep pagination enabled
|
|
341
|
-
- Limit slices (`queryset = queryset[:1000]`) for heavy searches
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
## 🤲 Contributing
|
|
346
|
-
|
|
347
|
-
1. Fork
|
|
348
|
-
2. Create branch
|
|
349
|
-
3. Add tests
|
|
350
|
-
4. Run lint (`ruff check .`)
|
|
351
|
-
5. Open PR
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
## ⭐ Support
|
|
356
|
-
|
|
357
|
-
Star the repo or donate:
|
|
358
|
-
|
|
359
|
-
- [Buy me a coffee](https://buymeacoffee.com/caspel26)
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
## 📜 License
|
|
364
|
-
|
|
365
|
-
MIT License. See [LICENSE](LICENSE).
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## 🔗 Quick Links
|
|
370
|
-
|
|
371
|
-
| Item | Link |
|
|
372
|
-
| ------- | -------------------------------------------------------- |
|
|
373
|
-
| PyPI | https://pypi.org/project/django-ninja-aio-crud/ |
|
|
374
|
-
| Docs | https://django-ninja-aio.com |
|
|
375
|
-
| Issues | https://github.com/caspel26/django-ninja-aio-crud/issues |
|
|
376
|
-
| Example | https://github.com/caspel26/ninja-aio-blog-example |
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
File without changes
|
{django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|