Repository: vitalik/django-ninja
Pull request #748 (2023-04-21 04:14:59)

Only title-case fields in schema if all lowercase verbose name
The more important purpose of this fix though, is that it changes the verbose_name to not be translated too early (see #646).

If the actual logical change isn't required, it's even simpler (but I think the smarter way  is more sensible).


Patch:
diff --git a/ninja/orm/fields.py b/ninja/orm/fields.py
index b760af9..7071081 100644
--- a/ninja/orm/fields.py
+++ b/ninja/orm/fields.py
@@ -15,6 +15,7 @@ from uuid import UUID
 
 from django.db.models import ManyToManyField
 from django.db.models.fields import Field
+from django.utils.functional import keep_lazy_text
 from pydantic import IPvAnyAddress
 from pydantic.fields import FieldInfo, Undefined
 
@@ -23,6 +24,13 @@ from ninja.openapi.schema import OpenAPISchema
 __all__ = ["create_m2m_link_type", "get_schema_field", "get_related_field_schema"]
 
 
+@keep_lazy_text
+def title_if_lower(s: str) -> str:
+    if s == s.lower():
+        return s.title()
+    return s
+
+
 class AnyObject:
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
@@ -141,7 +149,7 @@ def get_schema_field(field: Field, *, depth: int = 0) -> Tuple:
         default = Undefined
 
     description = field.help_text
-    title = field.verbose_name.title()
+    title = title_if_lower(field.verbose_name)
 
     return (
         python_type,
@@ -173,6 +181,6 @@ def get_related_field_schema(field: Field, *, depth: int) -> Tuple[OpenAPISchema
         FieldInfo(
             default=default,
             description=field.help_text,
-            title=field.verbose_name.title(),
+            title=title_if_lower(field.verbose_name),
         ),
     )


Test patch:
diff --git a/tests/test_orm_metaclass.py b/tests/test_orm_metaclass.py
index 911183e..97f5535 100644
--- a/tests/test_orm_metaclass.py
+++ b/tests/test_orm_metaclass.py
@@ -44,7 +44,7 @@ def test_simple():
         "title": "SampleSchema2",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "firstname": {"title": "Firstname", "type": "string"},
         },
         "required": ["firstname"],
@@ -115,7 +115,7 @@ def test_model_fields_all():
         "title": "SomeSchema",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "field1": {"title": "Field1", "type": "string"},
             "field2": {"title": "Field2", "type": "string"},
         },
diff --git a/tests/test_orm_schemas.py b/tests/test_orm_schemas.py
index 14b3557..4e76bdf 100644
--- a/tests/test_orm_schemas.py
+++ b/tests/test_orm_schemas.py
@@ -33,7 +33,7 @@ def test_inheritance():
         "title": "ChildModel",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "parent_field": {"title": "Parent Field", "type": "string"},
             "parentmodel_ptr_id": {"title": "Parentmodel Ptr", "type": "integer"},
             "child_field": {"title": "Child Field", "type": "string"},
@@ -87,7 +87,7 @@ def test_all_fields():
         "title": "AllFields",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "bigintegerfield": {"title": "Bigintegerfield", "type": "integer"},
             "binaryfield": {
                 "title": "Binaryfield",
@@ -222,7 +222,7 @@ def test_django_31_fields():
         "title": "ModelNewFields",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "jsonfield": {"title": "Jsonfield", "type": "object"},
             "positivebigintegerfield": {
                 "title": "Positivebigintegerfield",
@@ -261,7 +261,7 @@ def test_relational():
         "title": "TestSchema",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "onetoonefield_id": {"title": "Onetoonefield", "type": "integer"},
             "foreignkey_id": {"title": "Foreignkey", "type": "integer"},
             "manytomanyfield": {
@@ -279,7 +279,7 @@ def test_relational():
         "title": "TestSchemaDeep",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "onetoonefield": {
                 "title": "Onetoonefield",
                 "allOf": [{"$ref": "#/definitions/Related"}],
@@ -300,7 +300,7 @@ def test_relational():
                 "title": "Related",
                 "type": "object",
                 "properties": {
-                    "id": {"title": "Id", "type": "integer"},
+                    "id": {"title": "ID", "type": "integer"},
                     "charfield": {"title": "Charfield", "type": "string"},
                 },
                 "required": ["charfield"],
@@ -323,7 +323,7 @@ def test_default():
         "title": "MyModel",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "default_static": {
                 "title": "Default Static",
                 "default": "hello",
@@ -373,7 +373,7 @@ def test_fields_exclude():
         "title": "SampleModel3",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "f1": {"title": "F1", "type": "string"},
             "f2": {"title": "F2", "type": "string"},
         },
@@ -421,7 +421,7 @@ def test_with_relations():
         "title": "Category",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "title": {"title": "Title", "maxLength": 100, "type": "string"},
         },
         "required": ["title"],
@@ -475,7 +475,7 @@ def test_custom_fields():
         "title": "SmallModel",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "f1": {"title": "F1", "type": "string"},
             "f2": {"title": "F2", "type": "string"},
             "custom": {"title": "Custom", "type": "integer"},
@@ -490,7 +490,7 @@ def test_custom_fields():
         "title": "SmallModel2",
         "type": "object",
         "properties": {
-            "id": {"title": "Id", "type": "integer"},
+            "id": {"title": "ID", "type": "integer"},
             "f1": {"title": "F1", "type": "integer"},
             "f2": {"title": "F2", "type": "string"},
         },