Skip to content

Path and Query Parameters

Model Controllers in Ninja Extra provide flexible ways to handle path and query parameters in your API endpoints when using the ModelEndpointFactory. This guide covers how to work with these parameters effectively.

Note: This guide is only useful if you are using a Custom ModelService and you are not interested in adding additional logic to the route handler.

Basic Path Parameters

Path parameters are part of the URL path and are typically used to identify specific resources:

from ninja_extra import ModelEndpointFactory, ModelControllerBase

@api_controller("/events")
class EventModelController(ModelControllerBase):
    # Basic path parameter for event ID
    get_event = ModelEndpointFactory.find_one(
        path="/{int:id}",  # int converter for ID
        lookup_param="id",
        schema_out=EventSchema
    )

lookup_param is the name of the parameter in the model that will be used to lookup the object.

Path Parameter Types

The following parameter types are supported:

@api_controller("/events")
class EventModelController(ModelControllerBase):
    # Integer parameter
    get_by_id = ModelEndpointFactory.find_one(
        path="/{int:id}",
        lookup_param="id",
        schema_out=EventSchema
    )

    # String parameter
    get_by_slug = ModelEndpointFactory.find_one(
        path="/{str:slug}",
        lookup_param="slug",
        schema_out=EventSchema
    )

    # UUID parameter
    get_by_uuid = ModelEndpointFactory.find_one(
        path="/{uuid:uuid}",
        lookup_param="uuid",
        schema_out=EventSchema
    )

    # Date parameter
    get_by_date = ModelEndpointFactory.find_one(
        path="/{date:event_date}",
        lookup_param="event_date",
        schema_out=EventSchema
    )

Query Parameters

Query parameters are added to the URL after the ? character and are useful for filtering, sorting, and pagination:

from typing import Optional
from ninja_extra import ModelEndpointFactory

@api_controller("/events")
class EventModelController(ModelControllerBase):
    # Endpoint with query parameters
    list_events = ModelEndpointFactory.list(
        path="/?category=int&status=str",  # Define query parameters
        schema_out=EventSchema,
        queryset_getter=lambda self, **kwargs: self.get_filtered_events(**kwargs)
    )

    def get_filtered_events(self, category: Optional[int] = None, 
                          status: Optional[str] = None, **kwargs):
        queryset = self.model.objects.all()

        if category:
            queryset = queryset.filter(category_id=category)
        if status:
            queryset = queryset.filter(status=status)

        return queryset

Combining Path and Query Parameters

You can combine both types of parameters in a single endpoint:

class EventQueryParamsModelService(ModelService):
    def get_category_events(
        self, 
        category_id: int, 
        status: Optional[str] = None, 
        date: Optional[date] = None, 
        **kwargs
    ):
        queryset = self.model.objects.filter(category_id=category_id)
        if status:
            queryset = queryset.filter(status=status)
        if date:
            queryset = queryset.filter(start_date=date)
        return queryset

@api_controller("/events")
class EventModelController(ModelControllerBase):
    service = EventQueryParamsModelService(model=Event)
    # Path and query parameters together
    get_category_events = ModelEndpointFactory.list(
        path="/{int:category_id}/events?status=str&date=date",
        schema_out=EventSchema,
        queryset_getter=lambda self, **kwargs: self.service.get_category_events(**kwargs)
    )

Custom Parameter Handling

You can implement custom parameter handling using object getters:

class CustomParamsModelService(ModelService):
    def get_by_slug(self, slug: str) -> Event:
        return self.model.objects.get(slug=slug)


@api_controller("/events")
class EventModelController(ModelControllerBase):
    service = CustomParamsModelService(model=Event)
    get_event = ModelEndpointFactory.find_one(
        path="/{str:slug}",
        lookup_param="slug",
        schema_out=EventSchema,
        object_getter=lambda self, slug, **kwargs: self.service.get_by_slug(slug)
    )

Async Parameter Handling

For async controllers, parameter handling works similarly:

class AsyncCustomParamsModelService(ModelService):
    async def get_filtered_events(self, **kwargs):
        @sync_to_async
        def get_events():
            queryset = self.model.objects.all()

            if kwargs.get('category'):
                queryset = queryset.filter(category_id=kwargs['category'])
            if kwargs.get('status'):
                queryset = queryset.filter(status=kwargs['status'])

            return queryset

        return await get_events()

@api_controller("/events")
class AsyncEventModelController(ModelControllerBase):
    service_type = AsyncCustomParamsModelService
    model_config = ModelConfig(
        model=Event,
        async_routes=True
    )

    list_events = ModelEndpointFactory.list(
        path="/?category=int&status=str",
        schema_out=EventSchema,
        queryset_getter=lambda self, **kwargs: self.service.get_filtered_events(**kwargs)
    )

Parameter Validation

You can add validation to your parameters using Pydantic models:

from datetime import date
from typing import Optional
from pydantic import BaseModel, Field

class EventQueryParams(BaseModel):
    category_id: Optional[int] = None
    status: Optional[str] = Field(None, pattern="^(active|inactive|draft)$")
    date_from: Optional[date] = None
    date_to: Optional[date] = None


class EventQueryParamsModelService(ModelService):
    def get_filtered_events(self, params: EventQueryParams):
        queryset = self.model.objects.all()

        if params.category_id:
            queryset = queryset.filter(category_id=params.category_id)
        if params.status:
            queryset = queryset.filter(status=params.status)
        if params.date_from:
            queryset = queryset.filter(start_date__gte=params.date_from)
        if params.date_to:
            queryset = queryset.filter(end_date__lte=params.date_to)

        return queryset


@api_controller("/events")
class EventModelController(ModelControllerBase):
    service_type = EventQueryParamsModelService
    model_config = ModelConfig(
        model=Event,
        async_routes=True
    )

    list_events = ModelEndpointFactory.list(
        path="/",
        schema_in=EventQueryParams,
        schema_out=EventSchema,
        queryset_getter=lambda self, query: self.service.get_filtered_events(query)
    )