Skip to content

Commit

Permalink
Merge pull request #217 from eadwinCode/model_config_extra_options
Browse files Browse the repository at this point in the history
feat: Model config extra options for Model Controller
  • Loading branch information
eadwinCode authored Dec 30, 2024
2 parents 6327357 + 63517ed commit 12af6ff
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 10 deletions.
22 changes: 17 additions & 5 deletions docs/api_controller/model_controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ from .models import Event
class EventModelController(ModelControllerBase):
model_config = ModelConfig(
model=Event,
schema_config=ModelSchemaConfig(read_only_fields=["id", "category"]),
schema_config=ModelSchemaConfig(
read_only_fields=["id", "category"],
# if you want to extra configuration to the generated schemas
# extra_config_dict={
# 'title': 'EventCustomTitle',
# 'populate_by_name': True
# }
),
)

api = NinjaExtraAPI()
Expand Down Expand Up @@ -79,6 +86,7 @@ The `ModelConfig` is a Pydantic schema designed for validating and configuring t
- `depth`: The depth for nesting schema generation.
- `read_only_fields`: A list of fields to be excluded when generating input schemas for create, update, and patch operations.
- `write_only_fields`: A list of fields to be excluded when generating output schemas for find_one and list operations.
- `extra_config_dict`: A dictionary of extra configuration to be added to the generated schemas. Options must be valid Pydantic configuration options.
- **pagination**: A requisite for the model `list/GET` operation to prevent sending `100_000` items at once in a request. The pagination configuration mandates a `ModelPagination` Pydantic schema object for setup. Options encompass:
- `klass`: The pagination class of type `PaginationBase`. The default is `PageNumberPaginationExtra`.
- `paginator_kwargs`: A dictionary value for `PaginationBase` initialization. The default is None.
Expand Down Expand Up @@ -305,8 +313,10 @@ class EventModelController(ModelControllerBase):
custom_handler=lambda self, data, **kw: self.handle_add_event_to_new_category(data, **kw)
)

def handle_add_event_to_new_category(self, data: CreateCategorySchema, **kw: Any) -> Category:
event = self.service.get_one(pk=kw['event_id'])
def handle_add_event_to_new_category(
self, data: CreateCategorySchema, event_id: int, **kw: Any
) -> Category:
event = self.service.get_one(pk=event_id)
category = Category.objects.create(title=data.title)
event.category = category
event.save()
Expand Down Expand Up @@ -361,8 +371,10 @@ class EventModelController(ModelControllerBase):
custom_handler=lambda self, data, **kw: self.handle_add_event_to_new_category(data, **kw)
)

async def handle_add_event_to_new_category(self, data: CreateCategorySchema, **kw: Any) -> Category:
event = await self.service.get_one_async(pk=kw['event_id'])
async def handle_add_event_to_new_category(
self, data: CreateCategorySchema, event_id: int, **kw: Any
) -> Category:
event = await self.service.get_one_async(pk=event_id)
category = Category.objects.create(title=data.title)
event.category = category
event.save()
Expand Down
24 changes: 22 additions & 2 deletions ninja_extra/controllers/model/schemas.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import typing as t

from django.core.exceptions import ImproperlyConfigured
from django.db.models import Model
from ninja.pagination import PaginationBase
from pydantic import BaseModel as PydanticModel
from pydantic import Field, field_validator

try:
from ninja_schema import __version__ as ninja_schema_version
from ninja_schema.errors import ConfigError
from ninja_schema.orm.factory import SchemaFactory
from ninja_schema.orm.model_schema import (
Expand All @@ -14,15 +16,24 @@
from ninja_schema.orm.model_schema import (
ModelSchemaConfigAdapter,
)

NINJA_SCHEMA_VERSION = tuple(map(int, ninja_schema_version.split(".")))
except Exception: # pragma: no cover
ConfigError = NinjaSchemaModelSchemaConfig = ModelSchemaConfigAdapter = (
SchemaFactory
) = None
NINJA_SCHEMA_VERSION = (0, 0, 0)


from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseSchema


def _is_ninja_schema_version_supported() -> bool:
if NINJA_SCHEMA_VERSION[1] >= 14 and NINJA_SCHEMA_VERSION[2] >= 1:
return True
raise ImproperlyConfigured("ninja-schema version 0.14.1 or higher is required")


class ModelPagination(PydanticModel):
"""
Model Controller Pagination Configuration
Expand Down Expand Up @@ -54,9 +65,10 @@ class ModelSchemaConfig(PydanticModel):
exclude: t.Set[str] = Field(set())
optional: t.Optional[t.Union[str, t.Set[str]]] = Field(default=None)
depth: int = 0
#

read_only_fields: t.Optional[t.List[str]] = Field(default=None)
write_only_fields: t.Optional[t.Union[t.List[str]]] = Field(default=None)
extra_config_dict: t.Optional[t.Dict[str, t.Any]] = Field(default=None)


class ModelConfig(PydanticModel):
Expand Down Expand Up @@ -146,6 +158,9 @@ def generate_all_schema(self) -> None:
exclude_fields = set(self.schema_config.exclude)
working_fields = working_fields - exclude_fields

if self.schema_config.extra_config_dict:
_is_ninja_schema_version_supported()

if not self.create_schema and "create" in self.allowed_routes:
create_schema_fields = self._get_create_schema_fields(
working_fields, model_pk
Expand All @@ -156,6 +171,7 @@ def generate_all_schema(self) -> None:
fields=list(create_schema_fields),
skip_registry=True,
depth=self.schema_config.depth,
**(self.schema_config.extra_config_dict or {}),
)

if not self.update_schema and "update" in self.allowed_routes:
Expand All @@ -166,7 +182,9 @@ def generate_all_schema(self) -> None:
working_fields, model_pk
)
self.update_schema = SchemaFactory.create_schema(
self.model, fields=list(create_schema_fields)
self.model,
fields=list(create_schema_fields),
**(self.schema_config.extra_config_dict or {}),
)

if not self.patch_schema and "patch" in self.allowed_routes:
Expand All @@ -180,6 +198,7 @@ def generate_all_schema(self) -> None:
optional_fields=list(create_schema_fields),
skip_registry=True,
depth=self.schema_config.depth,
**(self.schema_config.extra_config_dict or {}),
)

if not self.retrieve_schema:
Expand All @@ -192,6 +211,7 @@ def generate_all_schema(self) -> None:
fields=list(retrieve_schema_fields),
skip_registry=True,
depth=self.schema_config.depth,
**(self.schema_config.extra_config_dict or {}),
)

def _get_create_schema_fields(self, working_fields: set, model_pk: str) -> set:
Expand Down
2 changes: 1 addition & 1 deletion requirements-tests.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
django-stubs
mypy == 1.13.0
ninja-schema>=0.14.0
ninja-schema>=0.14.1
pytest
pytest-asyncio==0.24.0
pytest-cov
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def test_default_model_config():
"depth": 0,
"read_only_fields": None,
"write_only_fields": None,
"extra_config_dict": None,
}
assert model_config.create_route_info == {}
assert model_config.find_one_route_info == {}
Expand All @@ -55,7 +56,10 @@ def test_include_gen_schema():
model_config = ModelConfig(
model=Event,
allowed_routes=["list", "find_one"],
schema_config=ModelSchemaConfig(include=["title", "start_date", "end_date"]),
schema_config=ModelSchemaConfig(
include=["title", "start_date", "end_date"],
extra_config_dict={"title": "EventCustomTitle"},
),
)
assert model_config.create_schema is None
assert model_config.patch_schema is None
Expand Down Expand Up @@ -83,7 +87,7 @@ def test_include_gen_schema():
},
},
"required": ["id", "title", "start_date", "end_date"],
"title": "EventSchema",
"title": "EventCustomTitle",
"type": "object",
}

Expand Down

0 comments on commit 12af6ff

Please sign in to comment.