Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ninja/orm/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def create_schema(
fields: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
optional_fields: Optional[List[str]] = None,
fields_strict_null: bool = False,
custom_fields: Optional[List[Tuple[str, Any, Any]]] = None,
base_class: Type[Schema] = Schema,
) -> Type[Schema]:
Expand All @@ -66,6 +67,7 @@ def create_schema(
fld,
depth=depth,
optional=optional_fields and (fld.name in optional_fields),
strict_null=fields_strict_null,
)
definitions[fld.name] = (python_type, field_info)

Expand Down
12 changes: 10 additions & 2 deletions ninja/orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ def _validate(cls, v: Any, _):

@no_type_check
def get_schema_field(
field: DjangoField, *, depth: int = 0, optional: bool = False
field: DjangoField,
*,
depth: int = 0,
optional: bool = False,
strict_null: bool = False,
) -> Tuple:
"Returns pydantic field from django's model field"
alias = None
Expand Down Expand Up @@ -163,7 +167,11 @@ def get_schema_field(
]
raise ConfigError("\n".join(msg)) from e

if field.primary_key or blank or null or optional:
if strict_null:
if optional or null:
default = None
nullable = True
elif field.primary_key or blank or null or optional:
default = None
nullable = True

Expand Down
5 changes: 5 additions & 0 deletions ninja/orm/metaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class MetaConf:
fields: Optional[List[str]] = None
exclude: Union[List[str], str, None] = None
fields_optional: Union[List[str], str, None] = None
fields_strict_null: bool = False

@staticmethod
def from_schema_class(name: str, namespace: dict) -> "MetaConf":
Expand All @@ -26,13 +27,15 @@ def from_schema_class(name: str, namespace: dict) -> "MetaConf":
fields = getattr(meta, "fields", None)
exclude = getattr(meta, "exclude", None)
optional_fields = getattr(meta, "fields_optional", None)
fields_strict_null = getattr(meta, "fields_strict_null", False)

elif "Config" in namespace:
config = namespace["Config"]
model = config.model
fields = getattr(config, "model_fields", None)
exclude = getattr(config, "model_exclude", None)
optional_fields = getattr(config, "model_fields_optional", None)
fields_strict_null = getattr(config, "fields_strict_null", False)

warnings.warn(
"The use of `Config` class is deprecated for ModelSchema, use 'Meta' instead",
Expand Down Expand Up @@ -62,6 +65,7 @@ def from_schema_class(name: str, namespace: dict) -> "MetaConf":
fields=fields,
exclude=exclude,
fields_optional=optional_fields,
fields_strict_null=fields_strict_null,
)


Expand Down Expand Up @@ -109,6 +113,7 @@ def __new__(
fields=meta_conf.fields,
exclude=meta_conf.exclude,
optional_fields=meta_conf.fields_optional,
fields_strict_null=meta_conf.fields_strict_null,
custom_fields=custom_fields,
base_class=cls,
)
Expand Down
104 changes: 103 additions & 1 deletion tests/test_orm_metaclass.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Optional

import pytest
from django.db import models

from ninja import ModelSchema
from ninja import Field, ModelSchema
from ninja.errors import ConfigError


Expand Down Expand Up @@ -183,3 +185,103 @@ def test_model_schema_without_config():

class NoConfigSchema(ModelSchema):
x: int


def test_field_strict_null():
class ModelForTestFieldStrictNull(models.Model):
blank_false_null_false = models.CharField(blank=False, null=False)
blank_false_null_true = models.CharField(blank=False, null=True)
blank_true_null_false = models.CharField(blank=True, null=False)
blank_true_null_true = models.CharField(blank=True, null=True)

class Meta:
app_label = "tests"

class FieldStrictNullFalseSchema(ModelSchema):
class Meta:
model = ModelForTestFieldStrictNull
fields = "__all__"

class FieldStrictNullTrueSchema(ModelSchema):
class Meta:
model = ModelForTestFieldStrictNull
fields = "__all__"
fields_strict_null = True

class FieldStrictNullTrueWithOptionalFieldsSchema(ModelSchema):
blank_true_null_false: Optional[str] = Field(None)

class Meta:
model = ModelForTestFieldStrictNull
fields = [
"id",
"blank_false_null_false",
]
fields_strict_null = True
fields_optional = ["id", "blank_false_null_false"]

assert FieldStrictNullFalseSchema.json_schema() == {
"title": "FieldStrictNullFalseSchema",
"type": "object",
"properties": {
"id": {"title": "ID", "anyOf": [{"type": "integer"}, {"type": "null"}]},
"blank_false_null_false": {
"title": "Blank False Null False",
"type": "string",
},
"blank_false_null_true": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank False Null True",
},
"blank_true_null_false": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank True Null False",
},
"blank_true_null_true": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank True Null True",
},
},
"required": ["blank_false_null_false"],
}

assert FieldStrictNullTrueSchema.json_schema() == {
"title": "FieldStrictNullTrueSchema",
"type": "object",
"properties": {
"id": {"title": "ID", "type": "integer"},
"blank_false_null_false": {
"title": "Blank False Null False",
"type": "string",
},
"blank_false_null_true": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank False Null True",
},
"blank_true_null_false": {
"title": "Blank True Null False",
"type": "string",
},
"blank_true_null_true": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank True Null True",
},
},
"required": ["id", "blank_false_null_false", "blank_true_null_false"],
}

assert FieldStrictNullTrueWithOptionalFieldsSchema.json_schema() == {
"title": "FieldStrictNullTrueWithOptionalFieldsSchema",
"type": "object",
"properties": {
"id": {"title": "ID", "anyOf": [{"type": "integer"}, {"type": "null"}]},
"blank_false_null_false": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank False Null False",
},
"blank_true_null_false": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Blank True Null False",
},
},
}