Skip to content

Commit 1608a77

Browse files
authored
Merge pull request #316 from eadwinCode/ninja1_5_0_upgrade
chores: Ninja v1.5.0 Upgrade
2 parents 37536c8 + 0827ddc commit 1608a77

File tree

4 files changed

+155
-10
lines changed

4 files changed

+155
-10
lines changed

docs/api_controller/model_controller/02_model_configuration.md

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,54 @@ class EventModelController(ModelControllerBase):
130130

131131
## **Route Configuration**
132132

133-
You can customize individual route behavior using route info dictionaries:
133+
You can customize individual route behavior using route info dictionaries. Each route type (`create_route_info`, `list_route_info`, `find_one_route_info`, `update_route_info`, `patch_route_info`, `delete_route_info`) accepts various configuration parameters.
134+
135+
### **Common Route Parameters**
136+
137+
All route types support these common parameters:
138+
139+
| Parameter | Type | Default | Description |
140+
|-----------|------|---------|-------------|
141+
| `path` | `str` | varies by route | Custom path for the route |
142+
| `status_code` | `int` | varies by route | HTTP status code for successful responses |
143+
| `auth` | `Any` | NOT_SET | Authentication class or instance |
144+
| `throttle` | `BaseThrottle \| List[BaseThrottle]` | NOT_SET | Throttle class(es) for rate limiting |
145+
| `response` | `Any` | NOT_SET | Custom response configuration |
146+
| `url_name` | `str \| None` | None | Django URL name for the route |
147+
| `description` | `str \| None` | None | Detailed description for OpenAPI docs |
148+
| `operation_id` | `str \| None` | None | Custom operation ID for OpenAPI |
149+
| `summary` | `str \| None` | varies by route | Short summary for OpenAPI docs |
150+
| `tags` | `List[str] \| None` | None | Tags for grouping in OpenAPI docs |
151+
| `deprecated` | `bool \| None` | None | Mark route as deprecated in OpenAPI |
152+
| `by_alias` | `bool` | False | Use schema field aliases in response |
153+
| `exclude_unset` | `bool` | False | Exclude unset fields from response |
154+
| `exclude_defaults` | `bool` | False | Exclude fields with default values |
155+
| `exclude_none` | `bool` | False | Exclude None fields from response |
156+
| `include_in_schema` | `bool` | True | Include route in OpenAPI schema |
157+
| `permissions` | `List[BasePermission]` | None | Permission classes for the route |
158+
| `openapi_extra` | `Dict[str, Any] \| None` | None | Extra OpenAPI schema properties |
159+
160+
### **Route-Specific Parameters**
161+
162+
#### **Create Route (`create_route_info`)**
163+
- `custom_handler`: Custom handler function to override default create logic
164+
165+
#### **Update/Patch Routes (`update_route_info`, `patch_route_info`)**
166+
- `object_getter`: Custom function to retrieve the object
167+
- `custom_handler`: Custom handler function to override default update/patch logic
168+
169+
#### **Find One Route (`find_one_route_info`)**
170+
- `object_getter`: Custom function to retrieve the object
171+
172+
#### **Delete Route (`delete_route_info`)**
173+
- `object_getter`: Custom function to retrieve the object
174+
- `custom_handler`: Custom handler function to override default delete logic
175+
176+
#### **List Route (`list_route_info`)**
177+
- `queryset_getter`: Custom function to retrieve the queryset
178+
- `pagination_response_schema`: Custom pagination response schema
179+
180+
### **Basic Example**
134181

135182
```python
136183
@api_controller("/events")
@@ -148,7 +195,6 @@ class EventModelController(ModelControllerBase):
148195
"summary": "List all events",
149196
"description": "Retrieves a paginated list of all events",
150197
"tags": ["events"],
151-
"schema_out": CustomListSchema,
152198
},
153199
find_one_route_info={
154200
"summary": "Get event details",
@@ -158,6 +204,57 @@ class EventModelController(ModelControllerBase):
158204
)
159205
```
160206

207+
### **Advanced Configuration Example**
208+
209+
```python
210+
from ninja_extra import status
211+
from ninja_extra.permissions import IsAuthenticated, IsAdminUser
212+
from ninja_extra.throttling import AnonRateThrottle
213+
214+
@api_controller("/events")
215+
class EventModelController(ModelControllerBase):
216+
model_config = ModelConfig(
217+
model=Event,
218+
create_route_info={
219+
"summary": "Create a new event",
220+
"description": "Creates a new event with the provided data",
221+
"tags": ["events", "management"],
222+
"status_code": status.HTTP_201_CREATED,
223+
"permissions": [IsAuthenticated],
224+
"throttle": AnonRateThrottle(),
225+
"exclude_none": True,
226+
"openapi_extra": {
227+
"requestBody": {
228+
"content": {
229+
"application/json": {
230+
"examples": {
231+
"example1": {
232+
"summary": "Conference event",
233+
"value": {
234+
"title": "Tech Conference 2024",
235+
"start_date": "2024-06-01",
236+
"end_date": "2024-06-03"
237+
}
238+
}
239+
}
240+
}
241+
}
242+
}
243+
}
244+
},
245+
update_route_info={
246+
"summary": "Update event",
247+
"permissions": [IsAuthenticated, IsAdminUser],
248+
"exclude_unset": True,
249+
},
250+
delete_route_info={
251+
"summary": "Delete an event",
252+
"permissions": [IsAdminUser],
253+
"status_code": status.HTTP_204_NO_CONTENT,
254+
}
255+
)
256+
```
257+
161258
## **Async Routes Configuration**
162259

163260
Enable async routes and configure async behavior:

ninja_extra/controllers/model/endpoints.py

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
from django.db.models import Model as DjangoModel
55
from django.db.models import QuerySet
6+
from ninja.constants import NOT_SET, NOT_SET_TYPE
67
from ninja.pagination import PaginationBase
78
from ninja.params import Body
89
from ninja.signature import is_async
10+
from ninja.throttling import BaseThrottle
911
from pydantic import BaseModel as PydanticModel
1012

1113
from ninja_extra import status
@@ -95,6 +97,9 @@ def create(
9597
schema_out: t.Type[PydanticModel],
9698
path: str = "/",
9799
status_code: int = status.HTTP_201_CREATED,
100+
auth: t.Any = NOT_SET,
101+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
102+
response: t.Any = NOT_SET,
98103
url_name: t.Optional[str] = None,
99104
custom_handler: t.Optional[t.Callable[..., t.Any]] = None,
100105
description: t.Optional[str] = None,
@@ -129,7 +134,9 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
129134

130135
return route.post(
131136
working_path,
132-
response={status_code: schema_out},
137+
response=response
138+
if response is not NOT_SET
139+
else {status_code: schema_out},
133140
url_name=url_name,
134141
description=description,
135142
operation_id=operation_id,
@@ -143,6 +150,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
143150
include_in_schema=include_in_schema,
144151
permissions=permissions,
145152
openapi_extra=openapi_extra,
153+
auth=auth,
154+
throttle=throttle,
146155
)(create_item)
147156

148157
return ModelEndpointFunction(view_fun_setup=_setup)
@@ -155,6 +164,9 @@ def update(
155164
schema_in: t.Type[PydanticModel],
156165
schema_out: t.Type[PydanticModel],
157166
status_code: int = status.HTTP_200_OK,
167+
auth: t.Any = NOT_SET,
168+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
169+
response: t.Any = NOT_SET,
158170
url_name: t.Optional[str] = None,
159171
description: t.Optional[str] = None,
160172
object_getter: t.Optional[t.Callable[..., DjangoModel]] = None,
@@ -192,7 +204,9 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
192204
)
193205
return route.put(
194206
working_path,
195-
response={status_code: schema_out},
207+
response=response
208+
if response is not NOT_SET
209+
else {status_code: schema_out},
196210
url_name=url_name,
197211
description=description,
198212
operation_id=operation_id,
@@ -206,6 +220,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
206220
include_in_schema=include_in_schema,
207221
permissions=permissions,
208222
openapi_extra=openapi_extra,
223+
auth=auth,
224+
throttle=throttle,
209225
)(update_item)
210226

211227
return ModelEndpointFunction(_setup)
@@ -218,6 +234,9 @@ def patch(
218234
schema_in: t.Type[PydanticModel],
219235
schema_out: t.Type[PydanticModel],
220236
status_code: int = status.HTTP_200_OK,
237+
auth: t.Any = NOT_SET,
238+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
239+
response: t.Any = NOT_SET,
221240
url_name: t.Optional[str] = None,
222241
description: t.Optional[str] = None,
223242
object_getter: t.Optional[t.Callable[..., DjangoModel]] = None,
@@ -255,7 +274,9 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
255274
)
256275
return route.patch(
257276
working_path,
258-
response={status_code: schema_out},
277+
response=response
278+
if response is not NOT_SET
279+
else {status_code: schema_out},
259280
url_name=url_name,
260281
description=description,
261282
operation_id=operation_id,
@@ -269,6 +290,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
269290
include_in_schema=include_in_schema,
270291
permissions=permissions,
271292
openapi_extra=openapi_extra,
293+
auth=auth,
294+
throttle=throttle,
272295
)(patch_item)
273296

274297
return ModelEndpointFunction(_setup)
@@ -280,6 +303,9 @@ def find_one(
280303
lookup_param: str,
281304
schema_out: t.Type[PydanticModel],
282305
status_code: int = status.HTTP_200_OK,
306+
auth: t.Any = NOT_SET,
307+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
308+
response: t.Any = NOT_SET,
283309
url_name: t.Optional[str] = None,
284310
description: t.Optional[str] = None,
285311
object_getter: t.Optional[t.Callable[..., DjangoModel]] = None,
@@ -313,7 +339,9 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
313339
)
314340
return route.get(
315341
working_path,
316-
response={status_code: schema_out},
342+
response=response
343+
if response is not NOT_SET
344+
else {status_code: schema_out},
317345
url_name=url_name,
318346
description=description,
319347
operation_id=operation_id,
@@ -327,6 +355,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
327355
include_in_schema=include_in_schema,
328356
permissions=permissions,
329357
openapi_extra=openapi_extra,
358+
auth=auth,
359+
throttle=throttle,
330360
)(get_item)
331361

332362
return ModelEndpointFunction(_setup)
@@ -337,6 +367,9 @@ def list(
337367
schema_out: t.Type[PydanticModel],
338368
path: str = "/",
339369
status_code: int = status.HTTP_200_OK,
370+
auth: t.Any = NOT_SET,
371+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
372+
response: t.Any = NOT_SET,
340373
url_name: t.Optional[str] = None,
341374
description: t.Optional[str] = None,
342375
operation_id: t.Optional[str] = None,
@@ -378,7 +411,9 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
378411
list_items = paginate(pagination_class, **paginate_kwargs)(list_items)
379412
return route.get(
380413
working_path,
381-
response={
414+
response=response
415+
if response is not NOT_SET
416+
else {
382417
status_code: pagination_response_schema[schema_out] # type:ignore[index]
383418
},
384419
url_name=url_name,
@@ -394,11 +429,15 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
394429
include_in_schema=include_in_schema,
395430
permissions=permissions,
396431
openapi_extra=openapi_extra,
432+
auth=auth,
433+
throttle=throttle,
397434
)(list_items)
398435

399436
return route.get(
400437
working_path,
401-
response={status_code: t.List[schema_out]}, # type:ignore[valid-type]
438+
response=response
439+
if response is not NOT_SET
440+
else {status_code: t.List[schema_out]}, # type:ignore[valid-type]
402441
url_name=url_name,
403442
description=description,
404443
operation_id=operation_id,
@@ -412,6 +451,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
412451
include_in_schema=include_in_schema,
413452
permissions=permissions,
414453
openapi_extra=openapi_extra,
454+
auth=auth,
455+
throttle=throttle,
415456
)(list_items)
416457

417458
return ModelEndpointFunction(_setup)
@@ -422,6 +463,9 @@ def delete(
422463
path: str,
423464
lookup_param: str,
424465
status_code: int = status.HTTP_204_NO_CONTENT,
466+
auth: t.Any = NOT_SET,
467+
throttle: t.Union[BaseThrottle, t.List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
468+
response: t.Any = NOT_SET,
425469
url_name: t.Optional[str] = None,
426470
description: t.Optional[str] = None,
427471
object_getter: t.Optional[t.Callable[..., DjangoModel]] = None,
@@ -460,7 +504,7 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
460504
return route.delete(
461505
working_path,
462506
url_name=url_name,
463-
response={status_code: None},
507+
response=response if response is not NOT_SET else {status_code: None},
464508
description=description,
465509
operation_id=operation_id,
466510
summary=summary,
@@ -473,6 +517,8 @@ def _setup(model_controller_type: t.Type["ModelControllerBase"]) -> t.Callable:
473517
include_in_schema=include_in_schema,
474518
permissions=permissions,
475519
openapi_extra=openapi_extra,
520+
auth=auth,
521+
throttle=throttle,
476522
)(delete_item)
477523

478524
return ModelEndpointFunction(_setup)

ninja_extra/controllers/model/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.core.exceptions import ImproperlyConfigured
44
from django.db.models import Model
5+
from ninja import FilterSchema
56
from ninja.pagination import PaginationBase
67
from pydantic import BaseModel as PydanticModel
78
from pydantic import Field, field_validator
@@ -43,6 +44,7 @@ class ModelPagination(PydanticModel):
4344
klass: t.Type[PaginationBase] = PageNumberPaginationExtra
4445
paginator_kwargs: t.Optional[dict] = None
4546
pagination_schema: t.Type[PydanticModel] = PaginatedResponseSchema
47+
filter_schema: t.Optional[t.Type[FilterSchema]] = None
4648

4749
@field_validator("pagination_schema", mode="before")
4850
def validate_schema(cls, value: t.Any) -> t.Any:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ classifiers = [
4242

4343
requires = [
4444
"Django >= 2.2",
45-
"django-ninja == 1.4.5",
45+
"django-ninja == 1.5.0",
4646
"injector >= 0.19.0",
4747
"asgiref",
4848
"contextlib2"

0 commit comments

Comments
 (0)