Skip to content

Commit

Permalink
Add support for custom django fields (#1109)
Browse files Browse the repository at this point in the history
Using exclusively `django.db.fields.Field.get_internal_type` to
determine which Pydantic field to use in the built schemas makes it
difficult to create custom django fields. This PR adds support for a new
method `get_schema_type`, which allows using `get_internal_type` for its
intended purpose (determining database column types, etc.), while at the
same time allowing specifying which Pydantic schema field to use.
  • Loading branch information
RobertKolner committed Oct 18, 2024
1 parent 9ff32ec commit d61837d
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 3 deletions.
41 changes: 40 additions & 1 deletion docs/docs/guides/response/django-pydantic.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,48 @@ def modify_data(request, pk: int, payload: PatchDict[GroupSchema]):

for attr, value in payload.items():
setattr(obj, attr, value)

obj.save()

```

in this example the `payload` argument will be a type of `dict` only fields that were passed in request and validated using `GroupSchema`

### Custom fields

For each Django field it encounters, `ModelSchema` uses the default `Field.get_internal_type` method
to find the correct representation in Pydantic schema. This process works fine for the built-in field
types, but there are cases where the user wants to create a custom field, with its own mapping to
Pydantic field. Consider the following (barebones) example field:

```python
class TranslatedTextField(models.JSONField):
description = "Translated string represented as a JSON field"
languages = {"en": _("English"), "fr": _("French")}

def formfield(self, **kwargs):
is_required = not self.blank
return TextDictField(
widget=TranslatedTextFieldWidget(languages=self.languages), required=is_required
)
```

In all cases where this field is used in a model, we would like to use the following schema:

```python
class TranslatedTextFieldSchema(Schema):
en: Optional[str] = None
fr: Optional[str] = None
```

To achieve that, we can add a `get_schema_type` function to the django field and add it to supported
types in Django Ninja:

```python
class TranslatedTextField(models.JSONField):
...
def get_schema_type(self):
return "TranslatedTextField"

ninja.orm.fields.TYPES["TranslatedTextField"] = TranslatedTextFieldSchema
```
11 changes: 9 additions & 2 deletions ninja/orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,15 @@ def get_schema_field(
null = field_options.get("null", False)
max_length = field_options.get("max_length")

internal_type = field.get_internal_type()
python_type = TYPES[internal_type]
if hasattr(field, 'get_schema_type'):
internal_type = field.get_schema_type()
else:
internal_type = field.get_internal_type()

try:
python_type = TYPES[internal_type]
except KeyError as e:
raise KeyError("Type '{0}' isn't registered in ninja.orm.fields.TYPES".format(internal_type)) from e

if field.primary_key or blank or null or optional:
default = None
Expand Down

0 comments on commit d61837d

Please sign in to comment.