vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
7.03k stars 421 forks source link

Correct usage of ForeignKey Ids in Model Schema #1303

Open kevingill1966 opened 4 days ago

kevingill1966 commented 4 days ago

I want to write a CRUD style API. I retrieve the model data, and save the model data. I want to do that using the schema mapped from the Django Model.

When I try it, the schema serialises using the django model attribute name. However, the validator expects the _id suffix. I am looking for the recommended way to configure the ModelSchema below to generate a JSON version with the attribute "content_type_id", instead of "content_type".

Here is an example. Execute using "python manage.py shell"

from django.contrib.auth.models import Permission
from pprint import pp
from ninja import ModelSchema, Field

class PermissionSchema(ModelSchema):
    class Meta:
        model = Permission
        fields = "__all__"

perm = Permission.objects.first()
serialiser = PermissionSchema.from_orm(perm)
d = serialiser.dict()
pp(d)

# {'id': 5, 'name': 'Can add group', 'content_type': 2, 'codename': 'add_group'}

PermissionSchema.model_validate(d)

# Traceback (most recent call last):
#   File "<console>", line 1, in <module>
#   File "/home/vscode/.local/lib/python3.11/site-packages/pydantic/main.py", line 596, in model_validate
#     return cls.__pydantic_validator__.validate_python(
#            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# pydantic_core._pydantic_core.ValidationError: 1 validation error for PermissionSchema
# content_type_id
#   Field required [type=missing, input_value=<DjangoGetter: {'id': 5, ...codename': 'add_group'}>, input_type=DjangoGetter]
#     For further information visit https://errors.pydantic.dev/2.9/v/missing

If I move the field content_type to content_type_id, it can parse

d["content_type_id"] = d.pop("content_type")

PermissionSchema.model_validate(d)
# PermissionSchema(id=5, name='Can add group', content_type=2, codename='add_group')

Note - the property name in the schema is "content_type_id", but the generated output is "content_type"

pp(PermissionSchema.schema())

# {'properties': {'id': {'anyOf': [{'type': 'integer'}, {'type': 'null'}],
#                        'title': 'ID'},
#                 'name': {'maxLength': 255, 'title': 'Name', 'type': 'string'},
#                 'content_type_id': {'title': 'Content Type', 'type': 'integer'},
#                 'codename': {'maxLength': 100,
#                              'title': 'Codename',
#                              'type': 'string'}},
#  'required': ['name', 'content_type_id', 'codename'],
#  'title': 'PermissionSchema',
#  'type': 'object'}
kevingill1966 commented 4 days ago

I found a discussion on this issue: #444

The authors:

  1. defined a custom property for the _id attribute
  2. excluded the foreign key from the field in the Meta section
from django.contrib.auth.models import Permission
from pprint import pp
from ninja import ModelSchema, Field

class PermissionSchema(ModelSchema):
    content_type_id: int
    class Meta:
        model = Permission
        fields = ['id', 'name', 'codename']

perm = Permission.objects.first()
serialiser = PermissionSchema.from_orm(perm)
d = serialiser.dict()
pp(d)

# {'content_type_id': 2,  'id': 5,  'name': 'Can add group',  'codename': 'add_group'}

This dictionary validates correctly

x = PermissionSchema.model_validate(d)
x.dict()
# {'content_type_id': 2, 'id': 5, 'name': 'Can add group', 'codename': 'add_group'}

Is this the standard approach or is there a more automated mechanism?