Model Service
The Model Service layer in Ninja Extra handles all CRUD operations for your models. While the default implementation works well for simple cases, you can customize it for more complex scenarios.
Default Model Service
The default ModelService
implements both synchronous and asynchronous operations:
from ninja_extra.controllers.model.interfaces import ModelServiceBase, AsyncModelServiceBase
class ModelService(ModelServiceBase, AsyncModelServiceBase):
def __init__(self, model):
super().__init__(model=model)
...
This provides the following methods:
Synchronous Methods:
get_one(pk, **kwargs)
- Retrieve a single objectget_all(**kwargs)
- Retrieve all objectscreate(schema, **kwargs)
- Create a new objectupdate(instance, schema, **kwargs)
- Update an objectpatch(instance, schema, **kwargs)
- Partially update an objectdelete(instance, **kwargs)
- Delete an object
Asynchronous Methods:
get_one_async(pk, **kwargs)
- Async retrieveget_all_async(**kwargs)
- Async retrieve allcreate_async(schema, **kwargs)
- Async createupdate_async(instance, schema, **kwargs)
- Async updatepatch_async(instance, schema, **kwargs)
- Async patchdelete_async(instance, **kwargs)
- Async delete
Custom Model Service
Here's how to create a custom service with additional business logic:
from typing import Any, List, Union
from django.db.models import QuerySet
from ninja_extra import ModelService
from pydantic import BaseModel
class EventModelService(ModelService):
def get_one(self, pk: Any, **kwargs: Any) -> Event:
# Add custom logic for retrieving an event
event = super().get_one(pk, **kwargs)
if not event.is_published and not kwargs.get('is_admin'):
raise PermissionError("Event not published")
return event
def get_all(self, **kwargs: Any) -> Union[QuerySet, List[Any]]:
# Filter events based on criteria
queryset = self.model.objects.all()
if not kwargs.get('is_admin'):
queryset = queryset.filter(is_published=True)
return queryset
def create(self, schema: BaseModel, **kwargs: Any) -> Any:
# Add custom creation logic
data = schema.model_dump(by_alias=True)
data['created_by'] = kwargs.get('user_id')
instance = self.model._default_manager.create(**data)
return instance
def update(self, instance: Event, schema: BaseModel, **kwargs: Any) -> Any:
# Add validation before update
if instance.is_locked:
raise ValueError("Cannot update locked event")
return super().update(instance, schema, **kwargs)
Async Model Service
For async operations, you can customize the async methods:
from ninja_extra import ModelService
from asgiref.sync import sync_to_async
class AsyncEventModelService(ModelService):
async def get_all_async(self, **kwargs: Any) -> QuerySet:
# Custom async implementation
@sync_to_async
def get_filtered_events():
queryset = self.model.objects.all()
if kwargs.get('category'):
queryset = queryset.filter(category_id=kwargs['category'])
return queryset
return await get_filtered_events()
async def create_async(self, schema: BaseModel, **kwargs: Any) -> Any:
# Custom async creation
@sync_to_async
def create_event():
data = schema.model_dump(by_alias=True)
data['created_by'] = kwargs.get('user_id')
return self.model._default_manager.create(**data)
return await create_event()
Using Custom Services
Attach your custom service to your Model Controller:
@api_controller("/events")
class EventModelController(ModelControllerBase):
service_type = EventModelService
model_config = ModelConfig(model=Event)
For async controllers:
@api_controller("/events")
class AsyncEventModelController(ModelControllerBase):
service_type = AsyncEventModelService
model_config = ModelConfig(
model=Event,
async_routes=True
)
Advanced Service Patterns
Service with Dependency Injection
Model Services support dependency injection, allowing you to inject other services and dependencies when the controller is instantiated. Here's a practical example using email notifications and user tracking:
from datetime import datetime
from typing import Any, Optional
from django.core.mail import send_mail
from ninja_extra import ModelService, api_controller, ModelConfig
from pydantic import BaseModel
from injector import inject
class EmailService:
"""Service for handling email notifications"""
def send_event_notification(self, event_data: dict, recipient_email: str):
subject = f"Event Update: {event_data['title']}"
message = (
f"Event Details:\n"
f"Title: {event_data['title']}\n"
f"Date: {event_data['start_date']} to {event_data['end_date']}\n"
)
send_mail(
subject=subject,
message=message,
from_email="events@example.com",
recipient_list=[recipient_email],
fail_silently=False,
)
class UserActivityService:
"""Service for tracking user activities"""
def track_activity(self, user_id: int, action: str, details: dict):
UserActivity.objects.create(
user_id=user_id,
action=action,
details=details,
timestamp=datetime.now()
)
EventModelService
with EmailService
and UserActivityService
as dependencies.
class EventModelService(ModelService):
"""
Event service with email notifications and activity tracking.
Dependencies are automatically injected by the framework.
"""
@inject
def __init__(
self,
model: Event,
email_service: EmailService,
activity_service: UserActivityService
):
super().__init__(model=model)
self.email_service = email_service
self.activity_service = activity_service
def create(self, schema: BaseModel, **kwargs: Any) -> Any:
# Create the event
event = super().create(schema, **kwargs)
# Track the creation activity
if user_id := kwargs.get('user_id'):
self.activity_service.track_activity(
user_id=user_id,
action="event_created",
details={
"event_id": event.id,
"title": event.title
}
)
# Send notification to organizer
if organizer_email := kwargs.get('organizer_email'):
self.email_service.send_event_notification(
event_data=schema.model_dump(),
recipient_email=organizer_email
)
return event
def update(self, instance: Event, schema: BaseModel, **kwargs: Any) -> Any:
# Update the event
updated_event = super().update(instance, schema, **kwargs)
# Track the update activity
if user_id := kwargs.get('user_id'):
self.activity_service.track_activity(
user_id=user_id,
action="event_updated",
details={
"event_id": updated_event.id,
"title": updated_event.title,
"changes": schema.model_dump()
}
)
# Notify relevant parties about the update
if notify_participants := kwargs.get('notify_participants'):
for participant in updated_event.participants.all():
self.email_service.send_event_notification(
event_data=schema.model_dump(),
recipient_email=participant.email
)
return updated_event
def delete(self, instance: Event, **kwargs: Any) -> Any:
event_data = {
"id": instance.id,
"title": instance.title
}
# Delete the event
super().delete(instance, **kwargs)
# Track the deletion
if user_id := kwargs.get('user_id'):
self.activity_service.track_activity(
user_id=user_id,
action="event_deleted",
details=event_data
)
# Notify participants about cancellation
if notify_participants := kwargs.get('notify_participants'):
for participant in instance.participants.all():
self.email_service.send_event_notification(
event_data={
**event_data,
"message": "Event has been cancelled"
},
recipient_email=participant.email
)
Creating EventModelController
with EventModelService
as the service.
from ninja_extra.controllers import ModelEndpointFactory, ModelControllerBase, ModelConfig
from ninja_extra import api_controller
@api_controller("/events")
class EventModelController(ModelControllerBase):
service_type = EventModelService
model_config = ModelConfig(model=Event, allowed_routes=['find_one', 'list'])
create_new_event = ModelEndpointFactory.create(
path="/?organizer_email=str",
schema_in=model_config.create_schema,
schema_out=model_config.retrieve_schema,
custom_handler=lambda self, data, **kw: self.service.create(data, **kw)
)
update_event = ModelEndpointFactory.update(
path="/{int:event_id}/?notify_participants=str",
lookup_param="event_id",
schema_in=model_config.update_schema,
schema_out=model_config.retrieve_schema,
object_getter=lambda self, pk, **kw: self.get_object_or_exception(self.model_config.model, pk=pk),
custom_handler=lambda self, **kw: self.service.update(**kw),
)
from injector import Module, singleton
class EventModule(Module):
def configure(self, binder):
binder.bind(EmailService, to=EmailService, scope=singleton)
binder.bind(UserActivityService, to=UserActivityService, scope=singleton)
## settings.py
```python
NINJA_EXTRA = {
'INJECTOR_MODULES': [
'your_app.injector_module.EventModule'
]
}
The injected services provide several benefits:
- Automatic email notifications when events are created/updated/deleted
- User activity tracking for audit trails
- Clean separation of business logic
- Easy to extend with additional services
- Testable components with clear dependencies
For more information on dependency injection, please refer to the Dependency Injection page.