-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added DI support for Model Service for ModelController
- Loading branch information
1 parent
12af6ff
commit 8403c03
Showing
14 changed files
with
1,049 additions
and
29 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
docs/api_controller/model_controller/01_getting_started.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
# Getting Started with Model Controllers | ||
|
||
Model Controllers in Ninja Extra provide a powerful way to automatically generate CRUD (Create, Read, Update, Delete) operations for Django ORM models. They simplify API development by handling common database operations while remaining highly customizable. | ||
|
||
## **Installation** | ||
|
||
First, ensure you have Ninja Extra and ninja-schema installed: | ||
|
||
```bash | ||
pip install django-ninja-extra ninja-schema | ||
``` | ||
|
||
`ninja-schema` package is optional, but it's recommended for generating schemas. | ||
|
||
## **Basic Usage** | ||
|
||
Let's start with a simple example. Consider this Django model: | ||
|
||
```python | ||
from django.db import models | ||
|
||
class Category(models.Model): | ||
title = models.CharField(max_length=100) | ||
|
||
class Event(models.Model): | ||
title = models.CharField(max_length=100) | ||
category = models.OneToOneField( | ||
Category, null=True, blank=True, | ||
on_delete=models.SET_NULL, | ||
related_name='events' | ||
) | ||
start_date = models.DateField() | ||
end_date = models.DateField() | ||
|
||
def __str__(self): | ||
return self.title | ||
``` | ||
|
||
To create a basic Model Controller for the Event model: | ||
|
||
```python | ||
from ninja_extra import ( | ||
ModelConfig, | ||
ModelControllerBase, | ||
api_controller, | ||
NinjaExtraAPI | ||
) | ||
from .models import Event | ||
|
||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
) | ||
|
||
# Register the controller with your API | ||
api = NinjaExtraAPI() | ||
api.register_controllers(EventModelController) | ||
``` | ||
|
||
This simple setup automatically creates the following endpoints: | ||
|
||
- `POST /events/` - Create a new event | ||
- `GET /events/{id}` - Retrieve a specific event | ||
- `PUT /events/{id}` - Update an event | ||
- `PATCH /events/{id}` - Partially update an event | ||
- `DELETE /events/{id}` - Delete an event | ||
- `GET /events/` - List all events (with pagination) | ||
|
||
It is important to that if `model_config.model` is not set, the controller becomes a regular NinjaExtra controller. | ||
|
||
## **Generated Schemas** | ||
|
||
The Model Controller automatically generates Pydantic schemas for your model using `ninja-schema`. These schemas handle: | ||
|
||
- Input validation | ||
- Output serialization | ||
- Automatic documentation in the OpenAPI schema | ||
|
||
For example, the generated schemas for our `Event` model would look like this: | ||
|
||
```python | ||
# Auto-generated create/update schema | ||
class EventCreateSchema(Schema): | ||
title: str | ||
start_date: date | ||
end_date: date | ||
category: Optional[int] = None | ||
|
||
# Auto-generated retrieve schema | ||
class EventSchema(Schema): | ||
id: int | ||
title: str | ||
start_date: date | ||
end_date: date | ||
category: Optional[int] = None | ||
``` | ||
|
||
## **Customizing Routes** | ||
|
||
You can control which routes are generated using the `allowed_routes` parameter: | ||
|
||
```python | ||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
allowed_routes=["list", "find_one"] # Only generate GET and GET/{id} endpoints | ||
) | ||
``` | ||
|
||
## **Async Support** | ||
|
||
Model Controllers support `async` operations out of the box. Just set `async_routes=True`: | ||
|
||
```python | ||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
async_routes=True # Enable async routes | ||
) | ||
``` | ||
|
||
## **Next Steps** | ||
|
||
- Learn about [Model Configuration](02_model_configuration.md) for detailed schema and route customization | ||
- Explore [Model Services](03_model_service.md) for customizing CRUD operations | ||
- See how to use [Query and Path Parameters](04_parameters.md) effectively with `ModelEndpointFactory` |
224 changes: 224 additions & 0 deletions
224
docs/api_controller/model_controller/02_model_configuration.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
# Model Configuration | ||
|
||
The `ModelConfig` class in Ninja Extra provides extensive configuration options for Model Controllers. It allows you to customize schema generation, route behavior, and pagination settings. | ||
|
||
## **Basic Configuration** | ||
|
||
Here's a comprehensive example of `ModelConfig` usage: | ||
|
||
```python | ||
from ninja_extra import ( | ||
ModelConfig, | ||
ModelControllerBase, | ||
ModelSchemaConfig, | ||
api_controller, | ||
) | ||
from .models import Event | ||
|
||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
schema_config=ModelSchemaConfig( | ||
read_only_fields=["id", "created_at"], | ||
write_only_fields=["password"], | ||
include=["title", "start_date", "end_date", "category"], | ||
exclude=set(), # Fields to exclude | ||
depth=1, # Nesting depth for related fields | ||
), | ||
async_routes=False, # Enable/disable async routes | ||
allowed_routes=["create", "find_one", "update", "patch", "delete", "list"], | ||
) | ||
``` | ||
|
||
## **Schema Configuration** | ||
|
||
The `ModelSchemaConfig` class controls how Pydantic schemas are generated from your Django models: | ||
|
||
```python | ||
from ninja_extra import ModelConfig, ModelSchemaConfig | ||
|
||
# Detailed schema configuration | ||
schema_config = ModelSchemaConfig( | ||
# Include specific fields (use "__all__" for all fields) | ||
include=["title", "description", "start_date"], | ||
|
||
# Exclude specific fields | ||
exclude={"internal_notes", "secret_key"}, | ||
|
||
# Fields that should be read-only (excluded from create/update schemas) | ||
read_only_fields=["id", "created_at", "updated_at"], | ||
|
||
# Fields that should be write-only (excluded from retrieve schemas) | ||
write_only_fields=["password"], | ||
|
||
# Depth of relationship traversal | ||
depth=1, | ||
|
||
# Additional Pydantic config options | ||
extra_config_dict={ | ||
"title": "EventSchema", | ||
"description": "Schema for Event model", | ||
"populate_by_name": True | ||
} | ||
) | ||
|
||
model_config = ModelConfig( | ||
model=Event, | ||
schema_config=schema_config | ||
) | ||
``` | ||
|
||
## **Custom Schemas** | ||
|
||
You can provide your own Pydantic schemas instead of using auto-generated ones: | ||
|
||
```python | ||
from datetime import date | ||
from pydantic import BaseModel, Field | ||
|
||
class EventCreateSchema(BaseModel): | ||
title: str = Field(..., max_length=100) | ||
start_date: date | ||
end_date: date | ||
category_id: int | None = None | ||
|
||
class EventRetrieveSchema(BaseModel): | ||
id: int | ||
title: str | ||
start_date: date | ||
end_date: date | ||
category_id: int | None | ||
|
||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
create_schema=EventCreateSchema, | ||
retrieve_schema=EventRetrieveSchema, | ||
update_schema=EventCreateSchema, # Reuse create schema for updates | ||
) | ||
``` | ||
|
||
## **Pagination Configuration** | ||
|
||
Model Controllers support customizable pagination for list endpoints: | ||
|
||
```python | ||
from ninja.pagination import LimitOffsetPagination | ||
from ninja_extra import ( | ||
ModelConfig, | ||
ModelPagination | ||
) | ||
from ninja_extra.pagination import NinjaPaginationResponseSchema | ||
|
||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
# Configure pagination | ||
pagination=ModelPagination( | ||
klass=LimitOffsetPagination, | ||
pagination_schema=NinjaPaginationResponseSchema, | ||
paginator_kwargs={ | ||
"limit": 20, | ||
"offset": 100 | ||
} | ||
) | ||
) | ||
``` | ||
|
||
## **Route Configuration** | ||
|
||
You can customize individual route behavior using route info dictionaries: | ||
|
||
```python | ||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
# Customize specific route configurations | ||
create_route_info={ | ||
"summary": "Create a new event", | ||
"description": "Creates a new event with the provided data", | ||
"tags": ["events"], | ||
"deprecated": False, | ||
}, | ||
list_route_info={ | ||
"summary": "List all events", | ||
"description": "Retrieves a paginated list of all events", | ||
"tags": ["events"], | ||
}, | ||
find_one_route_info={ | ||
"summary": "Get event details", | ||
"description": "Retrieves details of a specific event", | ||
"tags": ["events"], | ||
} | ||
) | ||
``` | ||
|
||
## **Async Routes Configuration** | ||
|
||
Enable async routes and configure async behavior: | ||
|
||
```python | ||
@api_controller("/events") | ||
class AsyncEventModelController(ModelControllerBase): | ||
model_config = ModelConfig( | ||
model=Event, | ||
# Async-specific configurations | ||
async_routes=True, | ||
schema_config=ModelSchemaConfig( | ||
read_only_fields=["id"], | ||
depth=1 | ||
) | ||
) | ||
|
||
# Custom async service implementation | ||
service = AsyncEventModelService(model=Event) | ||
``` | ||
|
||
## **Configuration Inheritance** | ||
|
||
ModelConfig also support configuration inheritance: | ||
|
||
```python | ||
from ninja_extra.controllers import ModelConfig | ||
|
||
class BaseModelConfig(ModelConfig): | ||
async_routes = True | ||
schema_config = ModelSchemaConfig( | ||
read_only_fields=["id", "created_at", "updated_at"], | ||
depth=1 | ||
) | ||
|
||
@api_controller("/events") | ||
class EventModelController(ModelControllerBase): | ||
model_config = BaseModelConfig( | ||
model=Event, | ||
# Override or extend base configuration | ||
allowed_routes=["list", "find_one"] | ||
) | ||
``` | ||
|
||
## **Best Practices** | ||
|
||
1. **Schema Configuration**: | ||
- Always specify `read_only_fields` for auto-generated fields | ||
- Use `depth` carefully as it can impact performance | ||
- Consider using `exclude` for sensitive fields | ||
|
||
2. **Route Configuration**: | ||
- Limit `allowed_routes` to only necessary endpoints | ||
- Provide meaningful summaries and descriptions | ||
- Use tags for API organization | ||
|
||
3. **Pagination**: | ||
- Always set reasonable limits | ||
- Consider your data size when choosing pagination class | ||
- Use appropriate page sizes for your use case | ||
|
||
4. **Async Support**: | ||
- Enable `async_routes` when using async database operations | ||
- Implement custom async services for complex operations | ||
- Consider performance implications of async operations |
Oops, something went wrong.